因为之前一直写的都是前后端很分离的 SAP 项目,身份认证基本都由前端做掉了,所以身份授权全部都是基于 JWT Header 来做的。现在呢,因为要好好学习一个正经后端的技能了,所以用上了 Razor Pages,不过在浏览器中直接访问网页是不会在请求头里面自动附加 token 的,所以必须要拓展一下 JWT 的认证方式,让 JWT 也能通过 Cookie 传递。
首先我参考了微软的这篇文档,因为要在 Cookie 中读取 JWT,所以需要自己定义一个解析 JWT 的方式————TicketDataFormat
,如下所示:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using ZeekoUtilsPack.AspNetCore.Jwt;
namespace ZeekoBlog.Jwt
{
public class JwtCookieDataFormat : ISecureDataFormat<AuthenticationTicket>
{
// 在之前的博客中提到的 JWT 配置项,详情见:https://zeeko.1503.run/Article/14
private readonly JwtOptions _jwtOptions;
public JwtCookieDataFormat(JwtOptions jwtOptions)
{
_jwtOptions = jwtOptions;
}
// 只用来读取 JWT ,所以不需要加密方法
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
public AuthenticationTicket Unprotect(string protectedText)
{
return Unprotect(protectedText, null);
}
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var jwtHandler = new JwtSecurityTokenHandler();
var tokenParam = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = _jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _jwtOptions.Credentials.Key,
ValidateLifetime = true
};
try
{
var principal = jwtHandler.ValidateToken(protectedText, tokenParam, out SecurityToken validatedToken);
return new AuthenticationTicket(principal, new AuthenticationProperties(), CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (ArgumentException)
{
return null;
}
}
}
}
然后,需要在 StartUp.cs
中启用自定义的 Cookie 认证:
// 使用 JWT 保护 API
services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenOptions.JwTokenValidationParameters;
});
// 使用 Cookie 保护页面
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Zeeko/Login";
options.Cookie.Name = "tk";
options.Cookie.Path = "/";
options.TicketDataFormat = new JwtCookieDataFormat(tokenOptions.TokenOptions);
options.ClaimsIssuer = "Zeeko";
});
至此,我们的网站中已经配置了两种身份认证的架构(Schema),其中,默认的架构是 Cookies 认证,这样在 MVC Views 与 Razor Pages 中就免去配置认证架构,好处是可以避免 Razor Pages 配置认证架构的繁琐代码。
然后为了能够让 Web API 能够兼容以上两者,所以再创建一个 JWTAuthorizeAttribute
,如下所示:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
namespace ZeekoBlog.Filters
{
public class JwtAuthorizeAttribute : AuthorizeAttribute
{
public JwtAuthorizeAttribute()
{
AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme + "," +
JwtBearerDefaults.AuthenticationScheme;
}
}
}
参考文档:
https://stackoverflow.com/questions/45198275/asp-net-core-authorization-failed-for-user-null