什么是鉴权 #

鉴权实际上就是一种身份认证

由用户提供凭据,然后将其与存储在操作系统、数据库、应用或资源中的凭据进行比较。 在授权过程中,如果凭据匹配,则用户身份验证成功,可执行已向其授权的操作。 授权指判断允许用户执行的操作的过程。 也可以将身份验证理解为进入空间(例如服务器、数据库、应用或资源)的一种方式,而授权是用户可以对该空间(服务器、数据库或应用)内的哪些对象执行哪些操作。

常见的鉴权方式 #

  • HTTP Basic Authentication

这是 HTTP 协议实现的基本认证方式,我们在浏览网页时,从浏览器正上方弹出的对话框要求我们输入账号密码,正是使用了这种认证方式

  • Session + Cookie

利用服务器端的 session(会话)和浏览器端的 cookie 来实现前后端的认证,由于 http 请求时是无状态的,服务器正常情况下是不知道当前请求之前有没有来过,这个时候我们如果要记录状态,就需要在服务器端创建一个会话(session),将同一个客户端的请求都维护在各自的会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建 session,如果有则已经认证成功了,否则就没有认证。

  • Token

客户端在首次登录以后,服务端再次接收 HTTP 请求的时候,就只认 Token 了,请求只要每次把 Token 带上就行了,服务器端会拦截所有的请求,然后校验 Token 的合法性,合法就放行,不合法就返回 401(鉴权失败)

Token验证比较灵活,适用于大部分场景。常用的 Token 鉴权方式的解决方案是 JWTJWT 是通过对带有相关用户信息的进行加密,加密的方式比较灵活,可以根据需求具体设计。

  • OAuth

OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。我们常见的提供 OAuth 认证服务的厂商有支付宝、QQ 和微信。

OAuth 协议又有 1.0 和 2.0 两个版本。相比较 1.0 版,2.0 版整个授权验证流程更简单更安全,也是目前最主要的用户身份验证和授权方式。

如何使用 #

配置之前 在添加授权服务之前,请先确保Startup.cs 中Configure 是否添加了以下两个中间件:

app.UseAuthentication();
app.UseAuthorization();

添加Cookie 身份验证 #

使用说明

如果您使用的是 WebAPI,则该小节可忽略,通常 WebAPI 使用的是 JWT 授权方式,而非 Cookie

// Cookies单独身份验证
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, b =>
        {
            b.LoginPath = "/Home/Login";
        });

#

添加Jwt 身份验证 #

  • 安装 Mall3s.Extras.Authentication.JwtBearer 拓展包

  • Startup.cs 中注册 AddJwt 服务,注意,必须在 .AddControllers() 之前注册!!

// 默认授权机制,需授权的即可(方法)需贴 `[Authorize]` 特性
services.AddJwt();

// 启用全局授权,这样每个接口都必须授权才能访问,无需贴 `[Authorize]` 特性,推荐!!!!!!!!!❤️
// services.AddJwt<JwtHandler>(enableGlobalAuthorize:true);

注:如果项目使用了 services.AddSignalR(); 服务,那么该服务必须在 services.AddJwt 之后注册。

默认 JwtHandler 代码:

using Mall3s.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Mall3sApi.Web.Core;

public class JwtHandler : AppAuthorizeHandler
{
    public override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        // 这里写您的授权判断逻辑,授权通过返回 true,否则返回 false

        return Task.FromResult(true);
    }
}

自定义 Jwt 配置(默认无需配置

{
    "JWTSettings": {
    "validatelssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true "IssuerSigningKey": "你的密钥", // 密钥,string 类型,必须是复杂密钥,长度大于16 "ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true
    "ValidIssuer": "签发方", // 签发方,string 类型
    "ValidateAudience" : true, // 是否验证签收方,bool 类型,默认true "ValidAudience": "签收方", // 签收方,string 类型
    "ValidateLifetime" : true, //是否验证过期时间,bool类型,默认true,建议true "ExpiredTime": 20, // 过期时间,long 类型,单位分钟,默认20分钟
    "clockSkew": 5, // 过期时间容错值,long 类型,单位秒,默认5秒
    "Algorithm": "HS256" // 加密算法,string 类型,默认SecurityAlgorithms.HmacSha256
    }
}

系统安全注意事项 Mall3s 框架为了方便开发,已经自动添加了Jwt 默认配置。建议每个项目都应该单独配置IssuerSigningKey ,ValidIssuer ,ValidAudience 这三个。否则同样用了Mall3s 框架生成的Token 可能存在相互访问各自系统的风险。 温馨提示 目前支持的加密算法,详情请查阅SecurityAlgorithms (opens new window) 生成Token

// 生成token
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>()
{
    { "userId", user.Id },	// 存储Id
    { "Account",user.Account }, // 存储用户名
});

混合身份验证 #

// JWT 和Cookies 混合身份验证
services.AddJwt(options => { options.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
	options.LoginPath = "/Home/Login";
});
特别注意
如果启用了混合身份验证后,WebApi需在控制器/Action中指定Scheme类型为
JwtBearerDefaults.AuthenticationScheme ,如:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 
public class ApiServices : IDynamicApiController
{

}

如果不设置Scheme 那么在混合授权的Swagger 中将默认采用Cookie 方式,也就是授权失败会将整个登录页面内容返回。

高级自定义授权 #

Mall3s 框架提供了非常灵活的高级策略鉴权和授权方式,通过该策略授权方式可以实现任何自定义授权。

AppAuthorizeHandler #

Mall3s 框架提供了AppAuthorizeHandler 策略授权处理程序提供基类,只需要创建自己的Handler 继承它即可。如:JwtHandler :

using Mall3s.Authorization;
using Mall3s.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.JsonWebTokens;
namespace Mall3s.Web.Core
{
    /// <summary>
    /// JWT 授权自定义处理程序
    /// </summary>
    public class JwtHandler : AppAuthorizeHandler
    {
        /// <summary>
        /// 请求管道
        /// </summary>
        /// <param name="context"></param>
        /// <param name="httpContext"></param>
        /// <returns></returns>

        public override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
        {
            // 此处已经自动验证Jwt token 的有效性了,无需手动验证
            // 检查权限,如果方法是异步的就不用Task.FromResult 包裹,直接使用async/await 即可
            return Task.FromResult(CheckAuthorzie(httpContext));
        }
        /// <summary>
        /// 检查权限
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        private static bool CheckAuthorzie(DefaultHttpContext httpContext) 
        {
            // 获取权限特性
            var securityDefineAttribute = httpContext.GetMetadata<SecurityDefineAttribute>();
            if (securityDefineAttribute == null) return true;
            return " 查询数据库返回是否有权限";
         }
    }
}

之后注册JwtHandler 即可:

services.AddJwt\<JwtHandler\>();

10.3.2 完全自定义授权

有些时候可能针对不同的平台采用不一样的授权方式,比如合作信任的第三方机构可以免授权,这时候我们只需要重写HandleAsync 方法即可。如:

using Mall3s.Authorization;
using Mall3s.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Mall3s.Web.Core
{
    public class JwtHandler : AppAuthorizeHandler
    {
        public override async Task HandleAsync(AuthorizationHandlerContext context)
        {
            // 常规授权(可以判断是不是第三方)
            var isAuthenticated = context.User.Identity.IsAuthenticated;
            // 第三方授权自定义
            if( 是第三方){
                foreach (var requirement in pendingRequirements)
                {

                // 授权成功context.Succeed(requirement);
                }
            }
            // 授权失败
            else context.Fail();
        }
    }
}

授权特性及全局授权 #

默认情况下,所有的路由都是允许匿名访问的,所以如果需要对某个 ActionController 设定授权访问,只需要在 ActionController[AppAuthorize][Authorize] 特性即可。

如果需要对特定的 ActionController 允许匿名访问,则贴 [AllowAnonymous] 即可。

全局授权 #

services.AddJwt<JwtHandler>(enableGlobalAuthorize:true);

匿名访问 #

如果需要对特定的 ActionController 允许匿名访问,则贴 [AllowAnonymous] 即可。

自动刷新Token #

后端登录部分 #

当用户登录成功之后,返回 accessToken 字符串,之后通过 JWTEncryption.GenerateRefreshToken() 获取 刷新Token,并通过响应报文头返回,如:

// token
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>()
{
    { "UserId", user.Id }, // 存储Id
    { "Account",user.Account }, // 存储用户名
});
// 获取刷新token
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 43200); // 第二个参数是刷新token 的有效期(分钟),默认三十天
// 设置响应报文头httpContextAccessor.HttpContext.Response.Headers["access-token"] = accessToken;
httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;

用户登录成功之后把 accessTokenrefreshToken 一起返回给客户端存储起来。

后端授权Handler 部分

using Mall3s.Authorization;
using Mall3s.Core;
using Mall3s.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Mall3s.Web.Core
{
    /// <summary>
    /// JWT 授权自定义处理程序
    /// </summary>
    public class JwtHandler : AppAuthorizeHandler
    {
        /// <summary>
        /// 重写Handler 添加自动刷新收取逻辑
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task HandleAsync(AuthorizationHandlerContext context)
        {
            // 自动刷新token
            if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext()))
            {
                await AuthorizeHandleAsync(context);
            }
        	else context.Fail(); // 授权失败
        }
        /// <summary>
        /// 验证管道,也就是验证核心代码
        /// </summary>
        /// <param name="context"></param>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
        {
        	// 检查权限,如果方法时异步的就不用Task.FromResult 包裹,直接使用async/await 即可
        	return Task.FromResult(true);
        }
  }
} 

客户端部分 #

客户端每次请求需将 accessTokenrefreshToken 放到请求报文头中传送到服务端,格式为:

Authorization: Bearer 你的token
X-Authorization: Bearer 你的刷新token

:::caution 特别注意

AuthenticationX-Authentication 都必须添加 Bearer 前缀。

:::

其他补充

在正常开发中,refreshToken 无需每次请求携带,而是 accessToken 即将过期之后携带即可。可以在客户端自行判断 accessToken 是否即将过期。

如果 Token 过期,那么 Mall3s 将自动根据有效期内的 refreshToken 自动生成新的 AccessToken,并在 响应报文头 中返回,如:

access-token: 新的token
x-access-token: 新的刷新token

存储新的 Token

前端需要获取 响应报文头 新的 token 和刷新 token 替换之前在客户处存储旧的 token 和刷新 token。

获取Jwt 存储的信息 #

// 获取 `Jwt` 存储的信息
var userId = App.User?.FindFirstValue("键");

注意引入 System.Security.Claims 命名空间

获取不到 `Token` 信息说明

请确保 .AddJwt 服务已注册且启用了 全局授权 或该接口(方法)贴有 [Authorize] 特性。

上次更新: 3/20/2023, 3:51:59 AM