using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Voxel.Cryptography;
namespace Insurance.ApiTest.Controllers
{
[Route("api/[Controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
private readonly IConfiguration _Config;
public VerifyController(IConfiguration configuration)
{
_Config = configuration;
}
//ok
///
/// 驗證會員的身份與課程
///
/// 會員資訊
/// 沒有傳入要求的會員資訊
/// 課程不存在或會員沒有購買該課程
/// 認證成功(測試帳號請參考範例),傳回Jwt Token
/// 不是合法會員或課程已完訓過
///
/// 範例
/// {
/// "Account":"voxel",
/// "Password":"000",
/// "CourseCode":"001"
/// }
///
[AllowAnonymous]
[HttpGet("Login")]
[Produces("application/json")]
[ProducesResponseType(typeof(LoginResult), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(LoginResult), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(LoginResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(LoginResult), StatusCodes.Status401Unauthorized)]
public IActionResult Login(string user)
{
if (string.IsNullOrWhiteSpace(user))
{
var result = new LoginResult { Success = false, Message = "沒傳入使用者資料" };
return BadRequest(result);
}
//會員帳號|課程代碼|會員密碼
string sourceString = GetDecryptString(user);
string account = sourceString.Split('|')[0];
string password = sourceString.Split('|')[1];
string insuranceNo = sourceString.Split('|')[2];
//string courseCode = sourceString.Split('|')[3]; 客戶不要課程代碼
if (string.IsNullOrEmpty(account))
{
var result = new LoginResult { Success = false, Message = "沒傳入使用者資料" };
return BadRequest(result);
}
if (account == "voxel" || account == "guest")
{
var result = new
{
Success = true,
Message = "認證成功",
Token = GetJwtToken(new LoginUser { Account = account, Password = password })
};
return Ok(result);
#region
//if (GetCourseCodeList().Contains(courseCode))
//{
// var result = new
// {
// Success = true,
// Message = "認證成功",
// Token = GetJwtToken(new LoginUser { Account = account, CourseCode = courseCode, Password = password })
// };
// string jsonResult = JsonConvert.SerializeObject(result);
// return Ok(GetEncryptString(jsonResult));
//}
//else
//{
// var result = new LoginResult { Success = false, Message = $"課程代碼[{courseCode}]不存在" };
// string jsonResult = JsonConvert.SerializeObject(result);
// return NotFound(GetEncryptString(jsonResult));
//}
#endregion
}
else
{
var result = new LoginResult { Success = false, Message = "會員的帳號與密碼不正確" };
return Unauthorized(result);
}
}
[NonAction]
private string GetDecryptString(string data)
{
return MyAes2.Decrypt(_Config["Aes:Key"], _Config["Aes:IV"], data);
}
//ok
[NonAction]
public string GetJwtToken(LoginUser loginUser)
{
// STEP1: 依傳入的帳號與密碼做身份驗證
var user = new { Id = loginUser.Account, Name = "Jason" };
// STEP2: 建立使用者的 Claims 聲明,這會是 JWT Payload 的一部分
var userClaims = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.NameId, user.Id),
new Claim(JwtRegisteredClaimNames.GivenName, "Voxel"),
//new Claim("Role", "Guest") //自訂欄位
});
// STEP3: 產生對稱式加密 JWT Signature 的密鑰
// 這部分是選用,但此範例在 Startup.cs 中有設定 ValidateIssuerSigningKey = true
// 所以這裡必填
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_Config["Jwt:Key"]));
// STEP4: 建立 JWT TokenHandler 以及用於描述 JWT 的 TokenDescriptor
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _Config["Jwt:Issuer"],
Audience = _Config["Jwt:Issuer"],
Subject = userClaims,
Expires = DateTime.Now.AddSeconds(60),
SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
};
// 產出所需要的 JWT Token 物件
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
// 產出序列化的 JWT Token 字串
var serializeToken = tokenHandler.WriteToken(securityToken);
return serializeToken;
}
///
/// 回報課程已完訓
///
/// 會員的完訓資訊
/// 沒有傳入完訓資料
/// 完訓成功
/// JWT認證失敗
/// ///
/// 範例
/// {
/// "InsuranceNo":"保險證號",
/// "Code":"課程完訓碼"
/// }
///
[HttpGet("Finish")]
[Produces("text/json")]
[ProducesResponseType(typeof(FinishResult), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(FinishResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(FinishUnauthorizedResult), StatusCodes.Status401Unauthorized)]
public IActionResult Finish(string info)
{
object result;
if (info == null)
{
result = new FinishResult { InsuranceNo = string.Empty, Success = false, Message = "沒有傳入完訓資料" };
return BadRequest(result);
}
string sourceString = GetDecryptString(info);
//登錄證號|完訓碼
string insuranceNo = sourceString.Split('|')[0];
// string courseCode = sourceString.Split('|')[1];
string code = sourceString.Split('|')[1];
if (string.IsNullOrWhiteSpace(code))
{
result = new FinishResult { Success = false, Message = "沒有傳入完訓碼" };
return BadRequest(result);
}
result = new FinishResult { InsuranceNo = insuranceNo, Success = true, Message = "完成訓練" };
return Ok(result);
}
}
public class MyAes2
{
///
/// 驗證key和iv的長度(AES只有三種長度適用)
///
///
///
private static void ValidateKeyIV(string key, string iv)
{
//驗證key和iv都必須為128bits或192bits或256bits
List LegalSizes = new List() { 128, 192, 256 };
int keyBitSize = Encoding.UTF8.GetBytes(key).Length * 8;
int ivBitSize = Encoding.UTF8.GetBytes(iv).Length * 8;
if (!LegalSizes.Contains(keyBitSize) || !LegalSizes.Contains(ivBitSize))
{
throw new Exception($@"key或iv的長度不在128bits、192bits、256bits其中一個,輸入的key bits:{keyBitSize},iv bits:{ivBitSize}");
}
}
///
/// 加密後回傳base64String,相同明碼文字加密後的base64String結果會相同(類似雜湊),除非變更key或iv
/// 如果key和iv忘記遺失的話,資料就解密不回來
/// base64String若使用在Url的話,Web端記得做UrlEncode
///
///
///
///
///
public static string Encrypt(string key, string iv, string plain_text)
{
ValidateKeyIV(key, iv);
Aes aes = Aes.Create();
//aes.Mode = CipherMode.CBC;//非必須,但加了較安全
//aes.Padding = PaddingMode.PKCS7;//非必須,但加了較安全
ICryptoTransform transform = aes.CreateEncryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv));
byte[] bPlainText = Encoding.UTF8.GetBytes(plain_text);//明碼文字轉byte[]
byte[] outputData = transform.TransformFinalBlock(bPlainText, 0, bPlainText.Length);//加密
string encrypt = Base64UrlEncoder.Encode(outputData);
return encrypt;
}
///
/// 解密後,回傳明碼文字
///
///
///
///
///
public static string Decrypt(string key, string iv, string base64UrlString)
{
ValidateKeyIV(key, iv);
Aes aes = Aes.Create();
//aes.Mode = CipherMode.CBC;//非必須,但加了較安全
//aes.Padding = PaddingMode.PKCS7;//非必須,但加了較安全
ICryptoTransform transform = aes.CreateDecryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv));
byte[] bEnBase64String = null;
byte[] outputData = null;
try
{
bEnBase64String = Base64UrlEncoder.DecodeBytes(base64UrlString);//有可能base64String格式錯誤
outputData = transform.TransformFinalBlock(bEnBase64String, 0, bEnBase64String.Length);//有可能解密出錯
return Encoding.UTF8.GetString(outputData);//解密成功
}
catch (Exception ex)
{
//todo 寫Log
throw new Exception($@"解密失敗:{ex.Message}");
}
}
}
public class LoginUser
{
///
/// 會員帳號
///
public string Account { get; set; }
///
/// 會員密碼
///
public string Password { get; set; }
///
/// 課程代碼
///
public string CourseCode { get; set; }
}
public class LoginResult
{
///
/// 認證是否成功
///
public bool Success { get; set; }
///
/// 認證失敗時的錯誤訊息
///
public string Message { get; set; }
///
/// JSON Web Token
///
public string Token { get; set; }
}
public class FinishResult
{
///
/// 登錄證號
///
public string InsuranceNo { get; set; }
///
/// 認證是否成功
///
public bool Success { get; set; }
///
/// 認證失敗時的錯誤訊息
///
public string Message { get; set; }
}
public class FinishUnauthorizedResult
{
}
public class FinishInfo
{
///
/// 登錄證號
///
public string InsuranceNo { get; set; }
///
/// 課程代碼
///
public string CourseCode { get; set; }
///
/// 完訓碼
///
public string Code { get; set; }
}
}