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; } } }