本篇将在webapi项目中加入身份认证功能,仅对授权的用户提供服务,未授权的访问给出提示信息。
1. 在传统的web身份认证中,典型的认证过程是这样的:用户通过浏览器打开登录页,输入帐号/密码后提交,
Web服务器判断帐号/密码是否正确,如果正确会在服务器中生成一个Session对象标识用户身份,
同时在响应Header中设置对应的Cookie字符串传递回浏览器 ;
当用户再次访问页面的时候,浏览器发送的请求头会带上此Cookie字符串,Web服务器获取请求头中的Cookie字符串并解析,
然后与服务器中的Session对象比较,找出对应的Session,后续代码中就可以使用代表该用户身份的session信息了,如下图:
2. .net core webapi是以微服务的方式来提供数据和计算服务的,自然需要使用更加合适的身份验证模式,
为此微软为我们提供了基于JWT(JSON Web Token)的认证授权实现,这是一种基于token的鉴权机制,
它不需要在服务端用Session去保存用户的认证或会话信息,
只需要在请求/响应Header中加入签名后的字符串(称之为Token)就可以了,如下图:
可以看到已经不需要在Web服务器上单独保存Session信息了。
3. JWT认证生成的Token字符串格式如下 :
jouf980uojfosadjdfhaksd.sf23er4wehyuty3fasdf2sdzxz7xcvghret2kko9werdfartyy.cfg54dskj7nyuhj89sdfghj
JWT由三部分组成,分别是头信息、有效载荷、签名,中间以(.)分隔,每段的作用如下:
第一部分:header(头信息)
包含算法(默认是 HMAC SHA256)和token类型定义(采用json格式,形式如下),
对此json进行Base64URL加密生成一个字符串,形如 "jouf980uojfosadjdfhaksd" 。
1 { 2 "alg": "HS256", 3 "typ": "JWT" 4 }
第二部分:Payload(有效载荷)
用来存放实际需要传递的数据,也是一个 JSON 对象并进行Base64URL加密生成一个字符串,
形如 "sf23er4wehyuty3fasdf2sdzxz7xcvghret2kko9werdfartyy" 。
1 { 2 "sub": "testabcde", 3 "exp":"2020/01/02 09:20:39", 4 "jti":1034758934 5 "userid":9527 6 "username": "John Doe", 7 }
字段名称可以使用官方推荐的名称也可以自己定义,如下表所示:
序号 | 名称 | 描述 | 预定义 |
1 | iss (issuer) | 签发人 | 官方字段 |
2 | exp (expiration time) | 过期时间 | 官方字段 |
3 | sub (subject) | 主题 | 官方字段 |
4 | aud (audience) | 受众 | 官方字段 |
5 | nbf (Not Before) | 生效时间 | 官方字段 |
6 | iat (Issued At) | 签发时间 | 官方字段 |
7 | jti (JWT ID) | 编号 | 官方字段 |
8 | xxxx | 用户自定义 | 私有字段 |
... | xxxx | 用户自定义 | 私有字段 |
n | xxxx | 用户自定义 | 私有字段 |
第三部分:Signature(签名),先将 "编码过的header字符串"和"编码过的payload字符串" 用 "." 拼接起来,
然后使用 Header 里面指定的签名算法(默认是 HMAC SHA256)配合指定的密钥(secret)对它们签名,
最后将签名的结果用Base64URL加密生成一个字符串,形如 "cfg54dskj7nyuhj89sdfghj" 。
注:因为Base64URL实际是对字符串做了一下转换,大部分语言都是可以对其进行解密的,
所以对于敏感数据的传递需要先加密,加密方式可以参考本系列第十二篇。
4. .net5 core webapi中的编码实现。
有了前面对原理的了解再编码实现就比较简单了,步骤如下:
4.1 先安装 System.IdentityModel.Tokens.Jwt 和 Microsoft.AspNetCore.Authentication.JwtBearer 这两个包。
安装后在解决方案资源管理器中就可以看到这2个包了,如下:
4.2 在Startup类的 ConfigureServices( ) 方法中配置JWT,增加如下代码(红色标注):
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddControllers(); 4 5 services.AddScoped<IUserDao, MySqlUserDao>(); 6 7 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); 8 9 }
4.3 在 Startup 类的 Configure( ) 方法中启用JWT,代码如下(红色标注):
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 2 { 3 if (env.IsDevelopment()) 4 { 5 app.UseDeveloperExceptionPage(); 6 7 } 8 9 loggerFactory.AddFile("Logs/log{Date}.txt"); 10 11 app.UseRouting(); 12 13 app.UseAuthentication(); 14 15 app.UseAuthorization(); 16 17 app.UseEndpoints(endpoints => 18 { 19 endpoints.MapControllers(); 20 }); 21 }
4.4 在UsersController.cs中使用Jwt认证服务。
在使用Jwt认证服务之前,我们先访问一下API,看Jwt认证是否已经生效。
打开POSTMAN,访问http://localhost:52384/api/users,结果如下:
可以看到在终结点 ManageUsers( ) 没有使用Jwt认证时,可以正常访问。
在UsersController.cs中添加引用 using Microsoft.AspNetCore.Authorization;
然后在终结点ManageUsers()上加[Authorize]属性启用
Jwt认证服务,代码如下(红色标注):
1 [HttpGet] 2 [Authorize] 3 public ContentResult ManageUsers() 4 { 5 //... 6 }
重新编译项目,然后在POSTMAN中访问 http://localhost:52384/api/users,结果如下:
此时响应的是"401 Unauthorized" 没有授权,Jwt身份认证已经生效了。
另:如果要对UsersController中所有终结点都启用Jwt身份认证,只需要在UsersController类名上加[Authorize]属性
就可以了。