zoukankan      html  css  js  c++  java
  • 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证

    多租户

      如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离

      这里设计的权限数据全部存储在缓存中,每个租户单独建立缓存Key,见权限系统服务章节介绍。

    用户accesstoken

      accesstoken的定义就不多的介绍了,Abp其实就是直接使用微软IdentityModel这套组件,并且zero项目还直接使用的微软的用户角色相关管理,这里面性能存在一定的问题,而且使用相对比较复杂,还不利于扩展,其实功能就那么一些,完全没有必要用这一套用户角色管理。

      其实这里要做的核心扩展,就是自定义Claim信息,accesstoken信息其实是存在Claim集合里面的,我们可以自定义Claim信息,用户登录的时候,调用组件提供的接口,创建用户访问的accesstoken信息,我这里扩展了RoleIds集合信息和UserName信息,UserName在系统很多地方都可能用到,而RoleIds集合信息用于做权限控制。

      实现代码

    public class AuthTokenProvider: IAuthTokenProvider
        {
            private readonly TokenAuthConfiguration _configuration;
            public AuthTokenProvider(TokenAuthConfiguration configuration)
            {
                _configuration = configuration;
            }
            public AuthenticateResultModel Authenticate(LoginResultModel loginResultModel)
            {
                List<Claim> claims = new List<Claim>();
                claims.Add(new Claim(AbpClaimTypes.UserId, loginResultModel.UserId.ToString()));
                if (loginResultModel.TenantId.HasValue)
                {
                    claims.Add(new Claim(AbpClaimTypes.TenantId, loginResultModel.TenantId.ToString()));
                }
                claims.Add(new Claim(AbpClaimTypes.RoleIds, loginResultModel.RoleIds)); // 自定义RoleIds和UserName申明
                claims.Add(new Claim(AbpClaimTypes.UserName, loginResultModel.UserName));
    
                var accessToken = CreateAccessToken(claims);
    
                return new AuthenticateResultModel
                {
                    AccessToken = accessToken,
                    EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
                    ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                    UserId = loginResultModel.UserId
                };
            }
    
            private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
            {
                var now = DateTime.UtcNow;
    
                var jwtSecurityToken = new JwtSecurityToken(
                    issuer: _configuration.Issuer,
                    audience: _configuration.Audience,
                    claims: claims,
                    notBefore: now,
                    expires: now.Add(expiration ?? _configuration.Expiration),
                    signingCredentials: _configuration.SigningCredentials
                );
    
                return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            }
    
            private static List<Claim> CreateJwtClaims(List<Claim> claims)
            {
                var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier);
    
                // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
                claims.AddRange(new[]
                {
                    //new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
                });
    
                return claims;
            }
    
            private string GetEncrpyedAccessToken(string accessToken)
            {
                return SimpleStringCipher.Instance.Encrypt(accessToken, CoreModule.DefaultPassPhrase);
            }
        }

     

    Ng-alain前端页面Token

      ng-alain前端页面也有权限管理,主要是delon项目里面的权限管理,核心其实也是在Token的管理,ITokenService,定义了管理Token的接口,ITokenModel定义了token和一个自定义存储的数据字典,在路由拦截的时候,添加了对Token是否为空的验证,我们在登录的时候,对token进行赋值,退出登录清空token。

      默认Token是存储在LocalStorage里面的,根据需要,可以改为SessionStorage。

      用户登录

    /**
       * 登录结果回调
       * @param authenticateResult 登录结果
       */
      private processAuthenticateResult(authenticateResult: AuthenticateResultModel) {
        if (authenticateResult.accessToken) {
          // 设置用户Token信息
          this.tokenService.set({
            token: authenticateResult.accessToken, userId: authenticateResult.userId
            , expireInSeconds: authenticateResult.expireInSeconds,encryptedAccessToken:authenticateResult.encryptedAccessToken
          });
          // 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响
          this.startupSrv.load().then(() => {
            let url = this.tokenService.referrer.url || '/';
            if (url.includes('/passport')) url = '/';
            this.router.navigateByUrl(url);
          });
    
        } else {
          this.error = "用户名或密码错误!";
          return;
        }
      }

    退出登录

    logout() {
        this.tokenService.clear();
        this.signalRService.CloseSignalr();
        this.router.navigateByUrl(this.tokenService.login_url);
      }

      ng-alain权限管理

    export interface ITokenModel {
        [key: string]: any;
        token: string;
    }
    export interface AuthReferrer {
        url?: string;
    }
    export interface ITokenService {
        set(data: ITokenModel): boolean;
        /**
         * 获取Token,形式包括:
         * - `get()` 获取 Simple Token
         * - `get<JWTTokenModel>(JWTTokenModel)` 获取 JWT Token
         */
        get(type?: any): ITokenModel;
        /**
         * 获取Token,形式包括:
         * - `get()` 获取 Simple Token
         * - `get<JWTTokenModel>(JWTTokenModel)` 获取 JWT Token
         */
        get<T extends ITokenModel>(type?: any): T;
        clear(): void;
        change(): Observable<ITokenModel>;
        /** 获取登录地址 */
        readonly login_url: string;
        /** 获取授权失败前路由信息 */
        readonly referrer?: AuthReferrer;
    }

    http拦截

      由于Abp后端需要验证accesstoken,abp对错误信息和请求结果信息进行了封装,因此,需要替换ng-alain的http拦截器,自定义拦截器。

      自定义拦截器主要是对每一个http请求,从ITokenService里面获取token,添加到请求Header里面;以及对答复内容,提取答复内容和显示错误信息。

      添加请求Token

    protected addAuthorizationHeaders(headers: HttpHeaders): HttpHeaders {
            let authorizationHeaders = headers ? headers.getAll('Authorization') : null;
            if (!authorizationHeaders) {
                authorizationHeaders = [];
            }
    
            if (!this.itemExists(authorizationHeaders, (item: string) => item.indexOf('Bearer ') == 0)) {
                let token = (this.injector.get(DA_SERVICE_TOKEN) as ITokenService).get();
                // let token = this._tokenService.getToken();
                if (headers && token) {
                    headers = headers.set('Authorization', 'Bearer ' + token.token);
                }
            }
    
            return headers;
        }

      答复内容参照abp提供的前端框架代码即可

    Ng-alain前端权限验证

      也是delon项目里面的acl功能模块,只需要在用户登录成功的时候,把用户能够访问的操作Code集合传递给acl,以及在页面使用的地方,定义权限控制的Code即可

    // ACL:设置权限为全量
      // this.aclService.setFull(true);
      this.aclService.setAbility(res.operate);

    控件控制

    <button (click)="add()" acl [acl-ability]="'adminuser-add'" nz-button nzType="primary">新建</button>

    列表按钮控制

    {
                  text: '删除', type: 'del', acl:{ ability:['adminuser-delete']}, click: (item: any) => {
                    this.http.delete(`api/services/app/SEC_AdminUser/Delete?Id=${item.id}`).subscribe(() => this.st.reload());
                  }
                },

    路由控制

    { path: 'operate', loadChildren: './sec-operate/sec-operate.module#SecOperateModule', canActivate: [ACLGuard], data: {
                    guard: <ACLType>{ability: ['operate-mgt']}
                  }}, // 操作管理
  • 相关阅读:
    log4j.appender.stdout.layout.ConversionPattern
    log4j:WARN No appenders could be found for logger
    Eclipse中TODO的分类,以及自动去除
    Java泛型类型擦除导致的类型转换问题
    Java中泛型数组的使用
    Java泛型中的通配符的使用
    Java泛型类定义,与泛型方法的定义使用
    Java泛型的类型擦除
    jQuery查询性能考虑
    jQuery判断checkbox是否选中
  • 原文地址:https://www.cnblogs.com/spritekuang/p/10818858.html
Copyright © 2011-2022 走看看