15. 安全鉴权

15.1 什么是鉴权#

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

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

15.1.1 常见的鉴权方式#

  • HTTP Basic Authentication

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

  • Session + Cookie

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

  • Token

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

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

  • OAuth

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

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

15.2 如何使用#

15.2.1 添加 Cookie 授权#

// Cookies单独授权
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, b =>
{
b.LoginPath = "/Home/Login";
});

15.2.2 添加 Jwt 授权#

services.AddJwt();
特别注意

JWT 鉴权并未包含在 Furion 框架中,需要安装 Furion 框架提供的 Furion.Extras.Authentication.JwtBearer 拓展包。

  • 自定义 Jwt 配置(默认无需配置)
{
"JWTSettings": {
"ValidateIssuerSigningKey": 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秒
}
}
  • 生成 Token
// 生成 token
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>()
{
{ "UserId", user.Id }, // 存储Id
{ "Account",user.Account }, // 存储用户名
});

15.2.3 混合授权#

// JWT 和 Cookies 同时授权
services.AddJwt(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, b =>
{
b.LoginPath = "/Home/Login";
});

15.3 高级自定义授权#

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

15.3.1 AppAuthorizeHandler#

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

using Furion.Authorization;
using Furion.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.JsonWebTokens;
namespace Furion.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 App.GetService<IAuthorizationManager>().CheckSecurity(securityDefineAttribute.ResourceId);
}
}
}

之后注册 JwtHandler 即可:

services.AddJwt<JwtHandler>();

15.4 授权特性及全局授权#

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

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

15.4.1 全局授权#

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

15.4.2 匿名访问#

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

15.5 自动刷新 Token#

15.5.1 后端部分#

当用户登录成功之后,返回 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, 30); // 第二个参数是刷新 token 的有效期,默认三十天

15.5.2 后端授权 Handler 部分#

using Furion.Authorization;
using Furion.Core;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Furion.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);
}
}
/// <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(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;
// 解析服务
var authorizationManager = httpContext.RequestServices.GetService<IAuthorizationManager>();
// 检查授权
return authorizationManager.CheckSecurity(securityDefineAttribute.ResourceId);
}
}
}

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

15.5.3 客户端部分#

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

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

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

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

前端需要获取 响应报文 新的 token 和刷新 token 替换之前旧的 token 和刷新 token。

15.6 反馈与建议#

与我们交流

给 Furion 提 Issue


了解更多

想了解更多 鉴权授权 知识可查阅 ASP.NET Core - 安全和标识 章节。