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']}
                  }}, // 操作管理
  • 相关阅读:
    0309. Best Time to Buy and Sell Stock with Cooldown (M)
    0621. Task Scheduler (M)
    0106. Construct Binary Tree from Inorder and Postorder Traversal (M)
    0258. Add Digits (E)
    0154. Find Minimum in Rotated Sorted Array II (H)
    0797. All Paths From Source to Target (M)
    0260. Single Number III (M)
    0072. Edit Distance (H)
    0103. Binary Tree Zigzag Level Order Traversal (M)
    0312. Burst Balloons (H)
  • 原文地址:https://www.cnblogs.com/spritekuang/p/10818858.html
Copyright © 2011-2022 走看看