ASP.NET Core 3.1 WebApi 系列【四】使用JWT认证
上一篇中我们学习了使用AuTofac实现依赖注入,这一篇我们继续学习在ASP.Net Core 3.1 中使用JWT认证
JWT介绍:
JSON Web Tokens:一种基于JSON的、用于在网络上声明某种主张的令牌(token)。
jwt验证流程:
1.首先,前端通过 Web 表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个 HTTP POST 请求。建议的方式是通过 SSL 加密的传输(https 协议),从而避免敏感信息被嗅探。
2.后端核对用户名和密码成功后,将用户的 id 等其他信息作为 JWT Payload(有效载荷),将其与头部分别进行 Base64 编码拼接后签名,形成一个 JWT。形成的 JWT 就是一个形同 lll.zzz.xxx 的字符串。
3.后端将 JWT 字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在 localStorage 或 sessionStorage 上,退出登录时前端删除保存的 JWT 即可。
4.前端在每次请求时将 JWT 放入 HTTP Header 中的 Authorization 位。(解决 XSS 和 XSRF 问题)
5.后端检查是否存在,如存在验证 JWT 的有效性。例如,检查签名是否正确;检查 Token 是否过期;检查 Token 的接收方是否是自己(可选)。
6.验证通过后后端使用 JWT 中包含的用户信息进行其他逻辑操作,返回相应结果。
jwt验证流程图:
若还不清楚什么是JWT
的请先了解下什么是JWT,下面我们直接上步骤。
一、创建一个类,用来存储签发或者验证jwt时用到的信息,项目结构如下:
public class TokenManagement
{
public string Secret { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int AccessExpiration { get; set; }
public int RefreshExpiration { get; set; }
}
二、在 appsettings.json 增加jwt使用到的配置信息
"tokenManagement": {
"secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"issuer": "webapi.cn", //发行人
"audience": "WebApi", //受众
"accessExpiration": 30, //访问过期时间
"refreshExpiration": 60 //刷新过期时间
}
三、在startup类的ConfigureServices方法中增加读取配置信息
#region 获取tokenManagement
services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
#endregion
到目前为止,我们完成了一些基础工作,下面在webapi中注入jwt的验证服务,并在中间件管道中启用authentication中间件。
startup类中要引用jwt验证服务的命名空间
四、注入jwt服务
#region 注入jwt的验证服务
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,//是否调用对签名securityToken的SecurityKey进行验证
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,//将用于检查令牌的发行者是否与此发行者相同
ValidAudience = token.Audience,//检查令牌的受众群体是否与此受众群体相同
ValidateIssuer = false,//是否验证发行者
ValidateAudience = false//在令牌验证期间验证受众
};
});
#endregion
五、中间件管道中启用authentication中间件,在Configure
方法中启用验证
注意事项:app.UseAuthentication();必须在app.UseRouting()和 app.UseEndpoints之间。
//jwt app.UseAuthentication();
六、引入jwt验证服务的命名空间
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
至此完成了JWT验证的功能,下面就需要增加签发token的逻辑。
七、增加一个专门用来用户认证和签发token逻辑。
(1)新增命名为AuthenticationController的控制器;
private readonly IAuthenticateService _authService; public AuthenticationController(IAuthenticateService authService) { _authService = authService; } /// <summary> /// 获取token /// </summary> /// <param name="request"></param> /// <returns></returns> [HttpPost] public ActionResult RequestToken(UserEntity request) { return Ok(_authService.IsAuthenticated(request)); }
(2)新增命名为IAuthenticateService的接口;
public interface IAuthenticateService { OperationResult IsAuthenticated(UserEntity request); }
(3)新增命名为AuthenticateService的类并实现IAuthenticateService接口;
public class AuthenticateService : IAuthenticateService { private readonly TokenManagement _tokenManagement; public AuthenticateService(IOptions<TokenManagement> tokenManagement) { _tokenManagement = tokenManagement.Value; } public OperationResult IsAuthenticated(UserEntity request) { LoginService LoginService = new LoginService(); var loginResult = LoginService.Login(request); if (!loginResult.State) { return loginResult; } var claims = new[] { new Claim(ClaimTypes.Name,request.USERNAME), new Claim(ClaimTypes.Role,request.PASSWORD) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var jwtToken = new JwtSecurityToken( _tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials ); string token = string.Empty; token = new JwtSecurityTokenHandler().WriteToken(jwtToken); return OperationResult.Success(token); } }
注意事项: LoginService.Login(request);此方法是账户密码方法,测试时候可以默认返回想要的数据。
八、测试
(1)在测试用的APi,打上Authorize特性,表明需要授权!
先测试一个访问需要授权的接口,但没有携带token信息,返回是401,表示未授权。
(2)下面我们先通过认证接口,获取token
(3)在Startup类中,ConfigureServices方法里设置swagger添加提供jwt验证token
#region 添加提供jwt验证token m.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Description = "在下框中输入请求头中需要添加Jwt授权Token: Bearer Token", Name = "Authorization", BearerFormat = "JWT", Scheme = "Bearer" }); m.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] { } } }); #endregion
(4)f5运行,在图示位置加上之前获取的token值
注意事项:在token之前要加上“Bearer ”。
(5)再次执行api,执行成功。
(6)jwt不仅可以进行身份验证,还可以进行权限授权,只要打上[Authorize(Roles = "admin")]特性即可,具体内容这里不做陈述。感兴趣的朋友可以自己去深度学习。
九、总结
基于token的认证方式,让我们构建分布式/松耦合的系统更加容易。任何地方生成的token,只有拥有相同秘钥,就可以再任何地方进行签名校验。
当然要用好jwt认证方式,还有其他安全细节需要处理,比如palyload中不能存放敏感信息,使用https的加密传输方式等等,可以根据业务实际需要再进一步安全加固!
同时我们也发现使用token,就可以摆脱cookie的限制,所以JWT是移动app开发的首选!