SpringBoot淺析安全管理之高級(jí)配置
角色繼承
SpringBoot淺析安全管理之基于數(shù)據(jù)庫(kù)認(rèn)證中定義了三種角色,但是這三種角色之間不具備任何關(guān)系,一般來(lái)說(shuō)角色之間是有關(guān)系的,例如 ROLE_admin 一般既具有 admin 權(quán)限,又具有 user 權(quán)限。那么如何配置這種角色繼承關(guān)系呢?只需要開(kāi)發(fā)者在 Spring Security 的配置類(lèi)中提供一個(gè) RoleHierarchy 即可
@Bean RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user"; roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; }
配置完 RoleHierarchy 之后,具有 ROLE_dba 角色的用戶就可以訪問(wèn) 所有資源了,具有 ROLE_admin 角色的用戶也可以訪問(wèn)具有 ROLE_user 角色才能訪問(wèn)的資源。
動(dòng)態(tài)權(quán)限配置
使用 HttpSecurity 配置的認(rèn)證授權(quán)規(guī)則還是不夠靈活,無(wú)法實(shí)現(xiàn)資源和角色之間的動(dòng)態(tài)調(diào)整,要實(shí)現(xiàn)動(dòng)態(tài)配置 URL 權(quán)限,需要開(kāi)發(fā)者自定義權(quán)限配置,配置步驟如下傳送門(mén)
1. 數(shù)據(jù)庫(kù)設(shè)計(jì)
在 10.2節(jié) 數(shù)據(jù)庫(kù)的基礎(chǔ)上再增加一張資源表和資源角色關(guān)聯(lián)表,資源表中定義了用戶能夠訪問(wèn)的 URL 模式,資源角色表則定義了訪問(wèn)該模式的 URL 需要什么樣的角色
建表語(yǔ)句
CREATE TABLE `menu` ( `id` int(11) NOT NULL, `pattern` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `menu_role` ( `id` int(11) NOT NULL, `mid` int(11) DEFAULT NULL, `rid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化數(shù)據(jù)
INSERT INTO `menu`(`id`, `pattern`) VALUES (1, '/db/**'); INSERT INTO `menu`(`id`, `pattern`) VALUES (2, '/admin/**'); INSERT INTO `menu`(`id`, `pattern`) VALUES (3, '/user/**'); INSERT INTO `menu_role`(`id`, `mid`, `rid`) VALUES (1, 1, 1); INSERT INTO `menu_role`(`id`, `mid`, `rid`) VALUES (2, 2, 2); INSERT INTO `menu_role`(`id`, `mid`, `rid`) VALUES (3, 3, 3);
Menu 實(shí)體類(lèi)
public class Menu { private Integer id; private String pattern; private List<Role> roles; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } }
MenuMapper
@Mapper public interface MenuMapper { List<Menu> getAllMenus(); }
MenuMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.sang.mapper.MenuMapper"> <resultMap id="BaseResultMap" type="org.sang.model.Menu"> <id property="id" column="id"/> <result property="pattern" column="pattern"/> <collection property="roles" ofType="org.sang.model.Role"> <id property="id" column="rid"/> <result property="name" column="rname"/> <result property="nameZh" column="rnameZh"/> </collection> </resultMap> <select id="getAllMenus" resultMap="BaseResultMap"> SELECT m.*,r.id AS rid,r.name AS rname,r.nameZh AS rnameZh FROM menu m LEFT JOIN menu_role mr ON m.`id`=mr.`mid` LEFT JOIN role r ON mr.`rid`=r.`id` </select> </mapper>
2. 自定義FilterInvocationSecurityMetadataSource
Spring Security 中通過(guò) FilterInvocationSecurityMetadataSource 接口中的 getAttributes 方法來(lái)確定一個(gè)請(qǐng)求需要哪些角色,F(xiàn)ilterInvocationSecurityMetadataSource 接口默認(rèn)實(shí)現(xiàn)類(lèi)是 DefaultFilterInvocationSecurityMetadataSource ,參考 DefaultFilterInvocationSecurityMetadataSource 的實(shí)現(xiàn),開(kāi)發(fā)者可以定義自己的 FilterInvocationSecurityMetadataSource ,如下:
@Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { AntPathMatcher antPathMatcher = new AntPathMatcher(); @Autowired MenuMapper menuMapper; @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); List<Menu> allMenus = menuMapper.getAllMenus(); for (Menu menu : allMenus) { if (antPathMatcher.match(menu.getPattern(), requestUrl)) { List<Role> roles = menu.getRoles(); String[] roleArr = new String[roles.size()]; for (int i = 0; i < roleArr.length; i++) { roleArr[i] = roles.get(i).getName(); } return SecurityConfig.createList(roleArr); } } return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
代碼解釋?zhuān)?/p>
- 開(kāi)發(fā)者自定義 FilterInvocationSecurityMetadataSource 主要實(shí)現(xiàn)接口中的 getAttributes 方法,該方法的參數(shù)是一個(gè) FilterInvocation ,開(kāi)發(fā)者可以從 FilterInvocation 中提取當(dāng)前請(qǐng)求的 URL ,返回值是 Collection,表示當(dāng)前請(qǐng)求 URL 所需角色
- 創(chuàng)建一個(gè) AntPathMatcher 主要用來(lái)實(shí)現(xiàn) ant 風(fēng)格的 URL 匹配
- ((FilterInvocation) object).getRequestUrl(); 從參數(shù)中提取當(dāng)前請(qǐng)求的 URL
- menuMapper.getAllMenus(); 從數(shù)據(jù)庫(kù)中獲取所有的資源信息,即 menu 表以及 menu 所對(duì)應(yīng)的 role,在真實(shí)項(xiàng)目環(huán)境中,開(kāi)發(fā)者可以將資源信息緩存在 Redis 或者其他緩存數(shù)據(jù)庫(kù)中
- 然后遍歷資源信息,遍歷過(guò)程中獲取當(dāng)前請(qǐng)求的 URL 所需要的角色信息并返回。如果當(dāng)前請(qǐng)求的 URL 在資源表中不存在相應(yīng)的模式,就假設(shè)該請(qǐng)求登錄后即可訪問(wèn),即直接返回 ROLE_LOGIN
- getAllConfigAttributes 方法用來(lái)返回所有定義好的權(quán)限資源,Spring Security 在啟動(dòng)時(shí)會(huì)校驗(yàn)相關(guān)配置是否正確,如果不需要校驗(yàn),那么該方法直接返回 null 即可
- supports 方法返回 類(lèi)對(duì)象是否支持校驗(yàn)
3. 自定義 AccessDecisionManager
當(dāng)一個(gè)請(qǐng)求走完 FilterInvocationSecurityMetadataSource 中的 getAttributes 方法后,接下來(lái)就會(huì)來(lái)到 AccessDecisionManager 類(lèi)中進(jìn)行角色信息的比對(duì),自定義 AccessDecisionManager 如下:
@Component public class CustomAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) { Collection<? extends GrantedAuthority> auths = auth.getAuthorities(); for (ConfigAttribute configAttribute : ca) { if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && auth instanceof UsernamePasswordAuthenticationToken) { return; } for (GrantedAuthority authority : auths) { if (configAttribute.getAttribute().equals(authority.getAuthority())) { return; } } } throw new AccessDeniedException("權(quán)限不足"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
代碼解釋?zhuān)?/p>
- 自定義 AccessDecisionManager 并重寫(xiě) decide 方法,在該方法中判斷當(dāng)前登錄的用戶是否具備當(dāng)前請(qǐng)求 URL 所需要的角色信息,如果不具備,就拋出 AccessDeniedException 異常,否則不做任何事情
- decide 有三個(gè)參數(shù),第一個(gè)參數(shù)包含當(dāng)前登錄用戶的信息;第二個(gè)參數(shù)則是一個(gè) FilterInvocation 對(duì)象,可以獲取當(dāng)前請(qǐng)求對(duì)象等;第三個(gè)參數(shù)就是 UsernamePasswordAuthenticationToken 的實(shí)例,說(shuō)明當(dāng)前用戶已登錄,該方法到此結(jié)束,否則進(jìn)入正常的判斷流程,如果當(dāng)前用戶具備當(dāng)前請(qǐng)求需要的角色,那么方法結(jié)束
4. 配置
最后,在 Spring Security 中配置上邊的兩個(gè)自定義類(lèi),代碼如下:
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); } @Bean RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user"; roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(cfisms()); object.setAccessDecisionManager(cadm()); return object; } }) .and() .formLogin() .loginProcessingUrl("/login").permitAll() .and() .csrf().disable(); } @Bean CustomFilterInvocationSecurityMetadataSource cfisms() { return new CustomFilterInvocationSecurityMetadataSource(); } @Bean CustomAccessDecisionManager cadm() { return new CustomAccessDecisionManager(); } }
代碼解釋?zhuān)?/p>
- 此處 WebSecurityConfig 類(lèi)的定義是對(duì) 10.2節(jié) 中 WebSecurityConfig 定義的補(bǔ)充,主要修改了 configure(HttpSecurity http) 方法的實(shí)現(xiàn)并添加了兩個(gè) Bean
- object.setSecurityMetadataSource(cfisms()); object.setAccessDecisionManager(cadm());將定義的兩個(gè)實(shí)例設(shè)置進(jìn)去
到此這篇關(guān)于SpringBoot淺析安全管理之高級(jí)配置的文章就介紹到這了,更多相關(guān)SpringBoot高級(jí)配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA線程sleep()和wait()詳解及實(shí)例
這篇文章主要介紹了JAVA線程sleep()和wait()詳解及實(shí)例的相關(guān)資料,探討一下sleep()和wait()方法的區(qū)別和實(shí)現(xiàn)機(jī)制,需要的朋友可以參考下2017-05-05詳解Java如何實(shí)現(xiàn)數(shù)值校驗(yàn)的算法
給定一個(gè)字符串如何判斷它是否為數(shù)值類(lèi)型?本文將帶著大家學(xué)習(xí)一下如何利用Java實(shí)現(xiàn)這個(gè)判斷算法,感興趣的小伙伴可以學(xué)習(xí)一下2022-04-04Java用20行代碼實(shí)現(xiàn)抖音小視頻批量轉(zhuǎn)換為gif動(dòng)態(tài)圖
這篇文章主要介紹了Java用20行代碼實(shí)現(xiàn)抖音小視頻批量轉(zhuǎn)換為gif動(dòng)態(tài)圖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04啟動(dòng)SpringBoot報(bào)錯(cuò)Input length = 1問(wèn)題及解決
這篇文章主要介紹了啟動(dòng)SpringBoot報(bào)錯(cuò)Input length = 1問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05