.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类,方便在其他方法中调用。

 

posted @   屏风马  阅读(2023)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示