JWT颁发以及权限校验实践(.net core)

977 阅读5分钟

权限设计》关于权限设计的一些方案,这里是使用.net core来实现jwt的授权验证,为了方便平时快速接入,开箱即用。jwt有token发行端(JwtSecurityTokenExtension)和接收端验证(PermissionsControl)两块内容,Github源码

Jwt Token发行端

有时候只是简单用JWT没必要上Identity4,杀鸡焉用宅牛刀,JwtSecurityTokenExtension类库生成token使用了微软的System.IdentityModel.Tokens.Jwt,加密算法是对称加密(HS256),里面有创建token以及刷新token的方法,讲下关键步骤和代码思路,具体代码可以自行去github翻看JwtSecurityTokenExtension类库

token密文包含的内容

token生成会默认带几个参数,上面的是头部信息,加密方式和类型,还有body这是必须有的两个时间,用于校验端检查token是否过期的。 生成的token可以通过 jwt.calebb.net/ 这个网站进行解析

{
    alg: "HS256",
    typ: "JWT"
}.
{
    exp:"jwt过期时间",
    nbf:"jwt生效时间"
}

你还可以你填写其他内容进去,CreateToken()方法的参数就是字典,把你需要传递的参数都转化在token里一起加密。

    public interface IJwtSecurityTokenExtension
    {
        TokenOut CreateToken(Dictionary<string, string> claimsContent = null);
        TokenOut RefreshToken(string refreshToken);
    }

返回值TokenOut里有token值和刷新token的值,以及给前端验证token是否过期的过期时间,过期时间前端可以先校验一遍是否过期。

    public class TokenOut
    {
        /// <summary>
        /// token
        /// </summary>
        public string Token { get; set; }
        /// <summary>
        /// token过期时间
        /// </summary>
        public DateTime TokenExpires { get; set; }
        /// <summary>
        /// 刷新token
        /// </summary>
        public string RefreshToken { get; set; }
        /// <summary>
        /// refresh token过期时间
        /// </summary>
        public DateTime RefreshTokenExpires { get; set; }
    }

token 和 refreshToken 的用途

客户端请求授权服务器会一起颁发两个token的过期时间比较短,refreshToken的过期时间比较长,如果token过期了,客户端会通过refreshToken去跟授权服务器换一个新的token。这样就不需要频繁登录,因为每次登录都需要用户去填写登录密码或者第三方授权。

针对这个用途,我刚开始搞这块的时候其实是有个疑问的,既然用refreshToken可以置换新token,那为何不直接把token的时间设置长些呢?简单粗暴何尝不好

image.png

其实这么搞也可以,但有时候处于安全考虑,双token可以提高那么一点点安全性(减少被截获token时候出现的攻击可能性),因为客户端和应用服务交互的频率是远远大于客户端和授权服务的交互,黑客劫持大概率能劫持到为客户端和应用服务交互的token,但因为token的过期时间短,相对安全。

更换 refreshToken 的存储方式

refreshToken的创建继承于IRefreshTokenStore,以TryAddSingleton方式注入。你可以新写你自己的refreshToken存储方式替换实现,Startup重新注入便会替换(TryAddSingleton特性)

    public interface IRefreshTokenStore
    {
        RefreshTokenModel Get(string refreshToken);
        void Set(string refreshToken, RefreshTokenModel model, TimeSpan expirationTime);
    }
    
    //注入
    services.TryAddSingleton<IRefreshTokenStore, RefreshTokenStore>();

Jwt Token校验端

一般是引用服务权限校验使用,《关于权限的设》计 这里有篇文章讲述几种方案(基于资源/基于角色/基于权限

Github源码的PermissionsControl模块分别实现了这几种方式的案例和组件。能应对比较多种情况不用动手撸轮子。

image.png

使用方式

只需要ConfigureServices注入便可,但只实现和基于权限和基于角色。

基于资源的统一通用性非常差,需要自己编写(在WebApp项目下的DocumentAuthorization有demo)。

image.png

[Authorize]只能验证token时间有没有过期,[UserPermissionCheck()] 和[RoleCheck("admin")] 分别验证权限和角色。

services.AddExtensionAuthorization(Configuration);

基于角色

如果你需要这个能识别到角色,需要在创建token时候加入,rol是约定

            var dic = new Dictionary<string, string>
            {
                {"rol", role},//角色
            };

            return _tokenHelper.CreateToken(dic);

[RoleCheck("admin")]就可以直接使用了

这种是针对一些角色跟权限挂钩的,并且权限变化少的项目。在开发时候定好角色就行。

基于权限

如果你需要这个能识别到权限,需要在创建token时候加入,per是约定

            var dic = new Dictionary<string, string>
            {
                {"per", role},//权限
            };

            return _tokenHelper.CreateToken(dic);

[PermissionCheck("update")]

和基于角色同理,可以传入权限,但如果不传,会根据当前控制器名字来校验权限,这种方式是针对应用于权限比较少的项目,因为权限名字全部存储在token里,token长度会增加,导致每次请求浪费大量网络资源。

所以延伸出

[UserPermissionCheck()]

使用这个标签需要自行实现IUserPermissionStore获取用户权限接口,这种是把权限持久化到存储媒介中,然后通过GetUserPermission()查询出来,那么token就无需带上用户所有权限了。其实如果做到这种程度,是否使用JWT也是个问题,session或者每次都查询redis校验更好,见仁见智把。

    public interface IUserPermissionStore
    {
        string [] GetUserPermission(object key);
    }

这种方式更适合于微服务体系中多系统(子系统)但用户体系相同的。用户以及其权限可以统一管理,但每个子系统的权限设计都千差万别,每个服务启动的时候都会把服务的所有权限同步到总的管理系统,便于统一管理。