**
**
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
因爲服務器不保存任何會話數據,即服務器變爲無狀態,使其更容易擴展
**
**