Skip to content

六 13、基于 RBAC 的访问权限控制功能功能配置

ZeroOrInfinity edited this page Dec 17, 2020 · 12 revisions

所属模块: core 模块

- [UriAuthorizeService](https://github.com/ZeroOrInfinity/UMS/blob/master/rbac/src/main/java/top/dcenter/ums/security/core/api/permission/service/UriAuthorizeService.java): `推荐通过实现 AbstractUriAuthorizeService 来实现此接口`
- [AbstractUriAuthorizeService](https://github.com/ZeroOrInfinity/UMS/blob/master/rbac/src/main/java/top/dcenter/ums/security/core/api/permission/service/AbstractUriAuthorizeService.java): `必须实现(Must implemente)`
    
    - uri(资源) 访问权限控制服务接口抽象类, 定义了基于(角色/多租户/SCOPE)的访问权限控制逻辑. 实现 AbstractUriAuthorizeService 抽象类并注入 IOC
     容器即可替换 DefaultUriAuthorizeService.  
    - 注意: 
    
      1\. 推荐实现 AbstractUriAuthorizeService 同时实现 UpdateAndCacheRolesResourcesService 更新与缓存权限服务, 有助于提高授权服务性能. 
      
      2\. 对传入的 Authentication 的 authorities 硬性要求: 
      ```java
       // 此 authorities 可以包含:  [ROLE_A, ROLE_B, ROLE_xxx TENANT_110110, SCOPE_read, SCOPE_write, SCOPE_xxx]
       // authorities 要求:
       //    1. 角色数量    >= 1
       //    2. SCOPE 数量 >= 0
       //    3. 多租户数量  = 1 或 0
       Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
      ```          
      3\. 此框架默认实现 `hasPermission(Authentication, HttpServletRequest)` 
      方法访问权限控制, 通过 `UriAuthoritiesPermissionEvaluator` 实现, 使用此接口的前提条件是: 应用使用的是 restful 风格的 API; 
      如果不是 restful 风格的 API, 请使用 `hasPermission(Authentication, String, String)` 接口的访问权限控制, 
      此接口使用注解的方式 `@PerAuthorize("hasPermission('/users', 'list')")` 来实现, 使用注解需先开启 `@EnableGlobalMethodSecurity(prePostEnabled = true)` 注解.
      
- [UpdateAndCacheRolesResourcesService](https://github.com/ZeroOrInfinity/UMS/blob/master/rbac/src/main/java/top/dcenter/ums/security/core/api/permission/service/UpdateAndCacheRolesResourcesService.java):    

    - 用于更新或缓存基于(角色/多租户/SCOPE)角色的权限的服务接口, 每次更新角色的 uri(资源)权限时,需要调用此接口, 推荐实现此 RolePermissionsService 接口, 会自动通过 AOP
     方式实现发布 UpdateRolesResourcesEvent 事件, 从而调用 UpdateAndCacheRolesResourcesService 对应的方法.
    - 建议:
     
        1\. 基于 角色 的权限控制: 实现所有角色 uri(资源) 的权限 Map(role, map(uri, Set(permission))) 的更新与缓存本机内存. 
        
        2\. 基于 SCOPE 的权限控制: 情况复杂一点, 但 SCOPE 类型比较少, 也还可以像 1 的方式实现缓存本机内存与更新. 
        
        3\. 基于 多租户 的权限控制: 情况比较复杂, 租户很少的情况下, 也还可以全部缓存在本机内存, 通常情况下全部缓存内存不现实, 只能借助于类似 redis 等的内存缓存.
     
- [RolePermissionsService](https://github.com/ZeroOrInfinity/UMS/blob/master/rbac/src/main/java/top/dcenter/ums/security/core/api/permission/service/RolePermissionsService.java):    

    - 更新与查询基于(角色/多租户/SCOPE)的角色资源服务接口. 主要用于给角色添加权限的操作. 
    - 注意: 
    
        1\. 在添加资源时, 通过PermissionType.getPermission() 来规范的权限格式, 因为要支持 restful 风格的 Api, 
            在授权时需要对 HttpMethod 与对应的权限进行匹配判断 
        
        2\. 如果实现了 UpdateAndCacheRolesResourcesService 接口, 未实现 RolePermissionsService 接口, 
            修改或添加基于"角色/多租户/SCOPE "的资源权限时一定要调用 UpdateAndCacheRolesResourcesService 对应的方法, 有两种方式: 一种发布事件, 另一种是直接调用对应服务; 
        ```java
        // 1. 推荐用发布事件(异步执行)
        applicationContext.publishEvent(new UpdateRolesResourcesEvent(true, ResourcesType.ROLE));
        applicationContext.publishEvent(new UpdateRolesResourcesEvent(true, ResourcesType.TENANT));
        applicationContext.publishEvent(new UpdateRolesResourcesEvent(true, ResourcesType.SCOPE));
        // 2. 直接调用服务
        // 基于角色
        UpdateAndCacheRolesResourcesService.updateAuthoritiesOfAllRoles();
        // 基于多租户
        UpdateAndCacheRolesResourcesService.updateAuthoritiesOfAllTenant();
        // 基于 SCOPE
        UpdateAndCacheRolesResourcesService.updateAuthoritiesOfAllScopes();
        ```
         
        3\. 实现此 RolePermissionsService 接口, 不需要执行上两种方法的操作, 已通过 AOP 方式实现发布 UpdateRolesResourcesEvent 事件.
        
        4\. 注意: RolePermissionsServiceAspect 切面**生效前提**, 事务的 `Order` 的值**必须 大于 1**, 如果是默认事务(优先级为 Integer.MAX_VALUE
            )不必关心这个值, 如果是自定义事务, 且设置了 Order 的值, 那么值**必须 大于 1**.

详细配置: demo 模块 -> permission-example

application.yml
server:
  port: 9090

spring:
  profiles:
    active: dev
  # mysql
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ums?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  # thymeleaf
  thymeleaf:
    encoding: utf-8
    prefix: classpath:/templates/
    suffix: .htm
    servlet:
      content-type: text/html;charset=UTF-8
  # jackson
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

  # session 简单配置
  session:
    # session 存储模式设置, 要导入相应的 spring-session 类的依赖, 默认为 none, 分布式服务应用把 session 放入 redis 等中间件
    store-type: none
    # session 过期时间
    timeout: PT1000s

  # 权限功能: jpa 配置
  jpa:
    generate-ddl: false
    show-sql: false



ums:
  oauth:
    # 是否支持第三方授权登录功能, 默认: 空, 必须明确配置是否支持
    enabled: false
  # 验证码配置
  codes:
    # 验证码缓存类型, 默认: SESSION, 可选: REDIS/SESSION
    validate-code-cache-type: session
    # 短信验证码
    sms:
      # 设置需要短信验证码认证的 uri(必须是非 GET 请求),多个 uri 用 “,”号分开支持通配符,如:/hello,/user/*;默认为 /authentication/form
      auth-urls:
        - /authentication/mobile
      request-param-mobile-name: mobile
      request-param-sms-code-name: smsCode

  # ================ 手机登录配置 ================
  mobile:
    login:
      # 手机验证码登录是否开启, 默认 false,
      # 手机验证码登录开启后 必须配置 ums.codes.sms.auth-urls=/authentication/mobile
      sms-code-login-is-open: true
      # 手机验证码登录请求处理url, 默认 /authentication/mobile
      login-processing-url-mobile: /authentication/mobile

  # RBAC 权限访问控制
  rbac:
    # ========= 权限表达式 ==========
    # 权限表达式, 当 enableRestfulApi=false 或者有 @EnableGlobalMethodSecurity 注释时生效, 默认为 isAuthenticated().
    # String accessExp = "isAuthenticated()";
    # // 配置等效与
    # httpSecurity.authorizeRequests().anyRequest().access(isAuthenticated());
    access-exp: isAuthenticated()
    # 权限表达式, 当 enableRestfulApi=true 且没有 @EnableGlobalMethodSecurity 注释时生效, 默认为 hasPermission(request, authentication).
    # hasPermission 表达式默认实现为 UriAuthoritiesPermissionEvaluator, 想自定义逻辑, 实现 PermissionEvaluator 即可替换.
    # String accessExp = "hasPermission(request, authentication)";
    # // 配置等效与
    # httpSecurity.authorizeRequests().anyRequest().access(hasPermission(request, authentication));
    restful-access-exp: hasPermission(request, authentication)
    # 是否支持 restful Api (前后端交互接口的风格; 如: 查询(GET),添加(POST),修改(PUT),删除(DELETE)), 默认: true.
    # 当 {@code enableRestfulApi=false} 时 {@code accessExp} 权限表达式生效,
    # 当 {@code enableRestfulApi=true} 时 {@code restfulAccessExp} 权限表达式生效.
    enable-restful-api: true
    # 用户角色层级配置,默认为 空. 分隔符为:" > ". 例如: ROLE_ADMIN 拥有 ROLE_USER 与 ROLE_VISIT 权限,
    # 可以表示为: ROLE_ADMIN > ROLE_USER > ROLE_VISIT 等价于下面的配置
    # 权限示例请看 permission-example 示例
    role-hierarchy:
      - ROLE_ADMIN > ROLE_USER
      - ROLE_USER > ROLE_VOTE

  client:    
    # 设置登录后返回格式(REDIRECT 与 JSON): 默认 JSON
    login-process-type: redirect
    # 一级域名(不包括二级域名) 例如:
    #       domain: www.example.com -> topDomain: example.com
    #       domain: www.example.com.cn -> topDomain: example.com.cn
    #       domain: aaa.bbb.example.net -> topDomain: example.net
    # 测试时用的 IP 或 localhost 直接原样设置就行.
    # 在应用启动时通过 SecurityAutoConfiguration 自动注入 MvcUtil 字段 topDomain 中. 如在设置跨域 cookie 时可以通过 MvcUtil.getTopDomain() 方法获取.
    topDomain: localhost
    # 登录页
    login-page: /login
    # 登录失败页
    failure-url: /login
    # 登录成功页
    success-url: /
    # 设置登出 url, 默认为 /logout
    logout-url: /logout
    # 设置登出后跳转的 url, 默认为 /login
    logout-success-url: /login
    # 不需要认证的静态资源 urls, 例如: /resources/**, /static/**
    ignoring-urls:
      - /static/**
    permit-urls:
      - /test/pass/**:GET
      - /addUser/**:GET
      - /addResource/**:GET
      - /addPermissionData/**:GET

    # =============== login routing 功能: 解决跳转登录成功后不能跳转原始请求的问题 ===============

    # 是否开启根据不同的uri跳转到相对应的登录页, 默认为: false, 当为 true 时还需要配置 loginUnAuthenticationRoutingUrl 和 authRedirectSuffixCondition
    open-authentication-redirect: true
    # 当请求需要身份认证时,默认跳转的url 会根据 authJumpSuffixCondition 条件判断的认证处理类型的 url,默认实现 /authentication/require,
    # 当 openAuthenticationRedirect = true 时生效.
    login-un-authentication-routing-url: /authentication/require
    # 设置 uri 相对应的跳转登录页, 例如:key=/**: value=/login.html, 用等号隔开key与value, 如: /**=/login.html, 默认为空.
    # 当 openAuthenticationRedirect = true 时生效.
    # 支持通配符, 匹配规则: /user/aa/bb/cc.html 匹配 pattern:/us?r/**/*.html, /user/**, /user/*/bb/c?.html, /user/**/*.*.
    # 规则具体看 AntPathMatcher.match(pattern, path)
    auth-redirect-suffix-condition:
      - '/hello=/login2'
      - '/user/**=/login'
      - '/order/**=/login'
      - '/file/**=/login'
      - '/social/**=/signIn.html'
#  mdc:
#    enable: true

---
spring:
  profiles: dev
  mvc:
    throw-exception-if-no-handler-found: true
  # 此配置其实与权限设置无关. 只是 permission-example 示例用到了 jpa, 在这里配置了一下
  jpa:
    generate-ddl: true
    show-sql: true
    database: mysql
    properties:
      hibernate:
        jdbc:
          batch_size: 500
          batch_versioned_data: true
          order_inserts: true
          order_updates: true
          order_removes: true
    hibernate:
      ddl-auto: update
  thymeleaf:
    cache: false
#  redis:
#    host: 192.168.88.88
#    port: 6379
#    password:
#    database: 0
#    # 连接超时的时间
#    timeout: 10000
#    # redis-lettuce-pool
#    lettuce:
#      # 会影响应用关闭是时间, dev 模式设置为 0
#      shutdown-timeout: PT0S
#      pool:
#        max-active: 8
#        max-wait: PT10S
#        max-idle: 8
#        min-idle: 1

logging:
  config: classpath:logback-spring.xml

#debug: true

server:
  servlet:
    context-path: /demo