security+jwt 動態權限控制RBAC0

**

圖片請看我的博客

**

RBAC0模型

最簡單的用戶、角色、權限模型。這裏面又包含了2種:

用戶和角色是多對一關係,即:一個用戶只充當一種角色,一種角色可以有多個用戶擔當。
用戶和角色是多對多關係,即:一個用戶可同時充當多種角色,一種角色可以有多個用戶擔當。

那麼,什麼時候該使用多對一的權限體系,什麼時候又該使用多對多的權限體系呢?

如果系統功能比較單一,使用人員較少,崗位權限相對清晰且確保不會出現兼崗的情況,此時可以考慮用多對一的權限體系。

其餘情況儘量使用多對多的權限體系,保證系統的可擴展性。
如:張三既是行政,也負責財務工作,那張三就同時擁有行政和財務兩個角色的權限。

項目環境

語言:kotlin(提供java版代碼)
構建:gradle
框架:springboot + dubbo + jpa
按業務做的是1用戶只能擁有1角色

FilterSecurityInterceptor

這個filter有幾個要素,如下:

  • SecurityMetadataSource (動態獲取url)
  • AccessDecisionManager (權限判斷)
  • AuthenticationManager (jwt配置)

動態獲取url權限配置

kotlin代碼:

@Component
class CustomMetadataSource : FilterInvocationSecurityMetadataSource {

  @Reference // 用的dubbo,所以這裏用這個注入
  var systemResourceService: SystemResourceService? = null

  private val antPathMatcher = AntPathMatcher()

  override fun getAttributes(o: Any): Collection<ConfigAttribute> {
    val fi = o as FilterInvocation
    // 請求地址
    val requestUrl = fi.requestUrl
    // 獲取系統權限資源配置 (這裏就是去動態獲取URL)
    val resourceAndRole = systemResourceService!!.getResourceAndRole()

    var needRoleList: MutableList<String> = ArrayList()

    for (resourceRoleMap in resourceAndRole) {
      // URL需要的角色,可能會有多個
      if (antPathMatcher.match(resourceRoleMap["url"], requestUrl)) {
        needRoleList.add(resourceRoleMap["role"].toString())
      }
    }

    if (needRoleList.size > 0) {
      val size = needRoleList.size
      val values = arrayOfNulls<String>(size)
      for (i in 0 until size) {
        values[i] = needRoleList[i]
      }
      return SecurityConfig.createList(*values)
    }

    // 沒有匹配上的資源,都是登錄訪問
    return SecurityConfig.createList("ROLE_LOGIN")
  }

  override fun getAllConfigAttributes(): Collection<ConfigAttribute>? {
    return null
  }

  override fun supports(clazz: Class<*>): Boolean {
    return FilterInvocation::class.java.isAssignableFrom(clazz)
  }

java代碼:

@Component
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    MenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) {
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        List<Menu> allMenu = menuService.getAllMenu();
        for (Menu menu : allMenu) {
            if (antPathMatcher.match(menu.getUrl(), requestUrl) && menu.getRoles().size() > 0) {
                List<Role> roles = menu.getRoles();
                int size = roles.size();
                String[] values = new String[size];
                for (int i = 0; i < size; i++) {
                    values[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(values);
            }
        }
        //沒有匹配上的資源,都是登錄訪問
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }

權限資源URL所需角色判斷

kotlin代碼:

@Component
class UrlAccessDecisionManager : AccessDecisionManager {

  private val log = LoggerFactory.getLogger(this.javaClass)

  override fun decide(authentication: Authentication, o: Any, cas: Collection<ConfigAttribute>) {
    val ite = cas.iterator()
    while (ite.hasNext()) {
      val ca = ite.next()

      // 當前請求需要的權限
      val needRole = ca.attribute

      // log.info("訪問" + o.toString() + "需要角色:" + needRole)

      if ("ROLE_LOGIN" == needRole) {
        if (authentication is AnonymousAuthenticationToken) {
          throw BadCredentialsException("未登錄")
        } else
          throw AccessDeniedException("權限不足")
      }

      // 遍歷判斷該url所需的角色看用戶是否具備
      for (ga in authentication.authorities) {
        if (ga.authority == needRole) {
          // 匹配到有對應角色,則允許通過
          return
        }
      }
    }
    throw AccessDeniedException("權限不足")
  }

  override fun supports(attribute: ConfigAttribute?): Boolean {
    return true
  }

  override fun supports(clazz: Class<*>?): Boolean {
    return true
  }

java代碼:

@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication auth, Object o, Collection<ConfigAttribute> cas) {
        Iterator<ConfigAttribute> iterator = cas.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            // 當前請求需要的權限
            String needRole = ca.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (auth instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登錄");
                } else {
                    throw new AccessDeniedException("權限不足");
                }
            }

            // 當前用戶所具有的權限
            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("權限不足");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

security配置

kotlin代碼:

@Configuration
@EnableWebSecurity
class WebSecurityConfig : WebSecurityConfigurerAdapter() {

  @Autowired
  var metadataSource: CustomMetadataSource? = null
  @Autowired
  var urlAccessDecisionManager: UrlAccessDecisionManager? = null
  @Autowired
  var deniedHandler: AuthenticationAccessDeniedHandler? = null

  // 設置 HTTP 驗證規則
  override fun configure(http: HttpSecurity) {
    // 關閉csrf驗證
    http.csrf().disable()
      // 對請求進行認證
      .authorizeRequests()
      // 所有 / 的所有請求 都放行
      .antMatchers("/").permitAll()
      // 所有 /login 的POST請求 都放行
      .antMatchers(HttpMethod.POST, "/login").permitAll()
      // 所有請求需要身份認證
      .anyRequest().authenticated()
      // 動態配置URL權限
      .withObjectPostProcessor(object : ObjectPostProcessor<FilterSecurityInterceptor> {
        override fun <O : FilterSecurityInterceptor> postProcess(o: O): O {
          o.securityMetadataSource = metadataSource
          o.accessDecisionManager = urlAccessDecisionManager
          return o
        }
      })
      // 權限不足處理器
      .exceptionHandling().accessDeniedHandler(deniedHandler)
  }

java代碼:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomMetadataSource metadataSource;
    @Autowired
    UrlAccessDecisionManager urlAccessDecisionManager;
    @Autowired
    AuthenticationAccessDeniedHandler deniedHandler;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                // 動態配置URL權限
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    public <O extends FilterSecurityInterceptor> O postProcess(
                            O fsi) {
                        fsi.setSecurityMetadataSource(metadataSource());
                        fsi.setAccessDecisionManager(urlAccessDecisionManager());
                        return fsi;
                    }
                })
                .and
                // 權限不足處理器
                .exceptionHandling().accessDeniedHandler(deniedHandler);
    }
}

至此,Spring Security動態配置url權限就完成了

如果無需使用jwt,下面的可以不用添加


增加JWT認證功能

將用戶id、擁有角色、過期時間放入token裏。不建議持久化Token

因爲服務器不保存任何會話數據,即服務器變爲無狀態,使其更容易擴展

**

添加JWT請看我的博客

**

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章