.net5 core webapi項目實戰之十六:身份驗證(下篇)


上一篇介紹了JWT身份認證的原理及.net core webapi中如何使用JWT。

 

本篇繼續介紹如何在客戶端設置JWT認證的Token信息以及Web服務器如何去解析Token中的內容並正確識別出用戶身份。

注:這里的客戶端可以是瀏覽器、桌面應用、手機APP、小程序等。

 

本項目中的認證流程是這樣的:

1. 用戶訪問登錄接口API(http://localhost:52384/api/users/login)   2. 得到 JWT Token  3. 后續API訪問的請求Header中加上此 Token

 

一、客戶端調用登錄接口生成Token字符串,代碼如下:

 1         [Route("login")]
 2         [HttpGet]
 3         public ContentResult LoginUser()
 4         {
 5             string acc = Request.Query["acc"]; //接收查詢參數acc
 6             string pwd = Request.Query["pwd"]; //接收查詢參數pwd
 7 
 8             //如果參數為空返回提示信息
 9             if (string.IsNullOrEmpty(acc) || string.IsNullOrEmpty(pwd))
10             { 
11                 return Content("{'result':'account or password is empty!'}");
12             }
13 
14             //生成 Token 信息
15             string token = GenerateJwtToken();
16  
17             //將 Token 信息返回給客戶端
18             return Content(token);
19         }
20 
21         private string GenerateJwtToken()
22         {
23             // 1. 設置加密算法
24             string algorithm = SecurityAlgorithms.HmacSha256;
25 
26             // 2. 生成簽名證書,注意密鑰長度至少為16位,否則會報錯
27             SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdefghijklmn1234567890"));
28             SigningCredentials signing = new SigningCredentials(key, algorithm);
29 
30             // 3. 構造Claims(任意添加幾個值,實際項目根據需要來設置要傳遞的信息)
31             Claim[] claims = new[]
32             {
33                 new Claim(JwtRegisteredClaimNames.Sub, "aaa000"),
34                 new Claim(ClaimTypes.Name, "ccc333"),
35             };
36 
37             // 4. 生成令牌
38             string issuer = null;
39             string audience = null;
40             DateTime notBefore = new DateTime(2020, 2, 1);
41             DateTime expires = new DateTime(2020, 3, 1);
42             JwtSecurityToken jwtToken = new JwtSecurityToken(issuer, audience, claims, notBefore, expires, signing);
43 
44             // 5. 將令牌實例轉換成字符串
45             string strToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
46 
47             return strToken;
48         }

 說明:

1 . [Route("login")] 表示訪問 LoginUser( ) 這個終結點時的路徑應該是 http://www.xxx.com/api/uses/login ;

2 . 用戶的賬號密碼是以查詢字符串的形式傳遞的 ,如 ?acc=123&pwd=456 ;

3 . 為了方便,這里僅判斷了賬號/密碼不為空,實際項目中可能需要和數據庫中的值做比較 ;

4 . 編碼時要添加 System.IdentityModel.Tokens.Jwt 和 System.Security.Claims 這兩個引用 ;

 

二、在終結點上加上屬性 [Authorize] 啟用身份認證,代碼如下:

1         [HttpGet]
2  [Authorize] 3         public ContentResult ManageUsers()
4         {
5             //...
6             //...
7         }    

 注:UsersController這個控制器中還有多個終結點,為了避免逐一添加的麻煩,可以將屬性 [Authorize] 加在類名上面,

使身份驗證的功能對該控制器中的每個終結點都生效,因 LoginUser( ) 這個終結點是不需要身份驗證的,

故加上屬性 [AllowAnonymous] 就可以了,代碼如下:

 1     [Route("api/[controller]")] 
 2     [ApiController]
 3  [Authorize]  4     public class UsersController : ControllerBase
 5     {
 6         private ILogger<UsersController> _logger;
 7         private IUserDao _userDao;
 8         public UsersController(ILogger<UsersController> logger, IUserDao userDao)
 9         { 
10             _logger = logger;
11             _userDao = userDao;
12         }
13 
14 
15     
16         [Route("login")]
17         [HttpGet]
18  [AllowAnonymous] 19         public ContentResult LoginUser()
20         {
21             string acc = Request.Query["acc"]; //接收查詢參數acc
22             string pwd = Request.Query["pwd"]; //接收查詢參數pwd
23 
24             //...
25             //...
26         }
27 
28     }

 

三、在Startup.cs的 ConfigureServices( ) 方法中設置身份認證的參數,代碼如下:

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             services.AddControllers();
 4      
 5             services.AddScoped<IUserDao, MySqlUserDao>();
 6 
 7  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(  8                 //設置Token驗證的參數
 9                 options =>
10                 {
11                     options.TokenValidationParameters = new TokenValidationParameters()
12                     {
13                         ValidateIssuer = false, //不驗證 issuer,因為 GenerateJwtToken() 方法生成的Token中issuer = null
14                         ValidateAudience = false, //同上
15 
16                         //如果希望 Token 在規定時間后失效請設置成 true
17                         //本例中 GenerateJwtToken()方法設置的值是 new DateTime(2020, 3, 1)
18                         ValidateLifetime = false,
19 
20                         //需要驗證簽名key, 同時IssuerSigningKey的值與 GenerateJwtToken() 方法中的key值保持一致
21                         ValidateIssuerSigningKey = true,
22                         IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdefghijklmn1234567890"))
23                     };
24                 }
25  ); 26         }

 

四、測試 LoginUser( ) 和 ManageUsers( ) 這兩個終結點。

4.1 重新編譯整個項目后打開POSTMAN訪問網址 http://localhost:52384/api/users/login,結果如下:

可以訪問且顯示賬號或密碼為空的信息。

4.2 加上查詢字符串后訪問網址:http://localhost:52384/api/users/login?acc=111&pwd=222,結果如下:

 如我們所願,生成了一個 JWT token字符串。

4.3 訪問網址 http://localhost:52384/api/users ,結果如下:

 無法訪問,提示401沒有權限的信息。

4.4 在請求頭中加上4.2中生成的Token字符串后重新訪問,結果如下:

可以正常訪問並得到期望的結果。

注:請求頭中加的 KEY 名稱是 Authorization ,VALUE 值是 Bearer + 空格 + token字符串, 空格個數不限,

Authorization Bearer 是關鍵字,不能寫錯。

 

========================================== 分割線 ==========================================

 補充:

1 . Claim[ ]數組中添加元素用JwtRegisteredClaimNames和ClaimTypes的區別

訪問網址 https://jwt.io/ , 在頁面中輸入4.2中生成的 token 解析出來的值如下:

JwtRegisteredClaimNames.Sub 生成的key名和屬性名保持一致,

ClaimTypes.Name生成的key帶了一個網址前綴。

2 . 在其他終結點中如何得到 "ccc333"這個值

添加終結點 DecodeToken( ) 並設置路由屬性為 [Route("decode")](對應網址為http://localhost:52384/api/users/decode),代碼如下:

 1         [Route("decode")]
 2         [HttpGet]
 3         public ContentResult DecodeToken()
 4         {
 5             //讀取 token 的值
 6             string token = Request.Headers["Authorization"];
 7             token = token.Replace(" ","").Substring(6);
 8 
 9             //得到 token 中payload部分的值
10             JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
11             JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
12             JwtPayload payload = jwtToken.Payload;
13 
14             //1. 官方字段可直接取值
15             string sub = payload.Sub;
16             string nbf = payload.Nbf.ToString();
17             string exp = payload.Exp.ToString();
18 
19             //2. 非官方字段反序列化成字典對象后取值
20             Dictionary<string,object> dic = JsonSerializer.Deserialize<Dictionary<string, object>>(payload.SerializeToJson());
21             string name = dic["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].ToString();
22              
23             //輸出
24             string jwt1 = "@@@@ " + payload.SerializeToJson() + Environment.NewLine;
25             string jwt2 = "@@@@ sub=" + sub + "; nbf=" + nbf + "; exp=" + exp + "; name=" + name + Environment.NewLine;
26              
27             return Content("result:" + Environment.NewLine + jwt1 + jwt2);
28         }

 訪問網址 http://localhost:52384/api/users/decode , 結果如下:

實際項目中可以將讀取Token內容的功能封裝成一個Utility類,方便在其他方法中調用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM