目录:网上冲浪指南

让网站的身份认证同时兼容 JWT 与 Cookie

2017/12/11

因为之前一直写的都是前后端很分离的 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://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x#configuration

https://stackoverflow.com/questions/45778679/dotnet-core-2-0-authentication-multiple-schemas-identity-cookies-and-jwt

https://stackoverflow.com/questions/45198275/asp-net-core-authorization-failed-for-user-null

本网站所展示的文章由 Zeeko Zhu 采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可

Zeeko's blog, Powered by ASP.NET Core 🐳