欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security動(dòng)態(tài)權(quán)限的實(shí)現(xiàn)方法詳解

 更新時(shí)間:2022年06月16日 11:15:36   作者:江南一點(diǎn)雨  
這篇文章主要和小伙伴們簡(jiǎn)單介紹下 Spring Security 中的動(dòng)態(tài)權(quán)限方案,以便于小伙伴們更好的理解 TienChin 項(xiàng)目中的權(quán)限方案,感興趣的可以了解一下

最近在做 TienChin 項(xiàng)目,用的是 RuoYi-Vue 腳手架,在這個(gè)腳手架中,訪問某個(gè)接口需要什么權(quán)限,這個(gè)是在代碼中硬編碼的,具體怎么實(shí)現(xiàn)的,松哥下篇文章來和大家分析,有的小伙伴可能希望能讓這個(gè)東西像 vhr 一樣,可以在數(shù)據(jù)庫(kù)中動(dòng)態(tài)配置,因此這篇文章和小伙伴們簡(jiǎn)單介紹下 Spring Security 中的動(dòng)態(tài)權(quán)限方案,以便于小伙伴們更好的理解 TienChin 項(xiàng)目中的權(quán)限方案。

1. 動(dòng)態(tài)管理權(quán)限規(guī)則

通過代碼來配置 URL 攔截規(guī)則和請(qǐng)求 URL 所需要的權(quán)限,這樣就比較死板,如果想要調(diào)整訪問某一個(gè) URL 所需要的權(quán)限,就需要修改代碼。

動(dòng)態(tài)管理權(quán)限規(guī)則就是我們將 URL 攔截規(guī)則和訪問 URL 所需要的權(quán)限都保存在數(shù)據(jù)庫(kù)中,這樣,在不改變?cè)创a的情況下,只需要修改數(shù)據(jù)庫(kù)中的數(shù)據(jù),就可以對(duì)權(quán)限進(jìn)行調(diào)整。

1.1 數(shù)據(jù)庫(kù)設(shè)計(jì)

簡(jiǎn)單起見,我們這里就不引入權(quán)限表了,直接使用角色表,用戶和角色關(guān)聯(lián),角色和資源關(guān)聯(lián),設(shè)計(jì)出來的表結(jié)構(gòu)如圖 13-9 所示。

圖13-9  一個(gè)簡(jiǎn)單的權(quán)限數(shù)據(jù)庫(kù)結(jié)構(gòu)

menu 表是相當(dāng)于我們的資源表,它里邊保存了訪問規(guī)則,如圖 13-10 所示。

圖13-10  訪問規(guī)則

role 是角色表,里邊定義了系統(tǒng)中的角色,如圖 13-11 所示。

圖13-11  用戶角色表

user 是用戶表,如圖 13-12 所示。

圖13-12  用戶表

user_role 是用戶角色關(guān)聯(lián)表,用戶具有哪些角色,可以通過該表體現(xiàn)出來,如圖 13-13 所示。

圖13-13  用戶角色關(guān)聯(lián)表

menu_role 是資源角色關(guān)聯(lián)表,訪問某一個(gè)資源,需要哪些角色,可以通過該表體現(xiàn)出來,如圖 13-14 所示。

圖13-14  資源角色關(guān)聯(lián)表

至此,一個(gè)簡(jiǎn)易的權(quán)限數(shù)據(jù)庫(kù)就設(shè)計(jì)好了(在本書提供的案例中,有SQL腳本)。

1.2 實(shí)戰(zhàn)

項(xiàng)目創(chuàng)建

創(chuàng)建 Spring Boot 項(xiàng)目,由于涉及數(shù)據(jù)庫(kù)操作,這里選用目前大家使用較多的 MyBatis 框架,所以除了引入 Web、Spring Security 依賴之外,還需要引入 MyBatis 以及 MySQL 依賴。

最終的 pom.xml 文件內(nèi)容如下:

<dependencies>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter-security</artifactId>
????</dependency>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter-web</artifactId>
????</dependency>
????<dependency>
????????<groupId>org.mybatis.spring.boot</groupId>
????????<artifactId>mybatis-spring-boot-starter</artifactId>
????????<version>2.1.3</version>
????</dependency>
????<dependency>
????????<groupId>mysql</groupId>
????????<artifactId>mysql-connector-java</artifactId>
????????<scope>runtime</scope>
????</dependency>
</dependencies>

項(xiàng)目創(chuàng)建完成后,接下來在 application.properties 中配置數(shù)據(jù)庫(kù)連接信息:

spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///security13?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

配置完成后,我們的準(zhǔn)備工作就算完成了。

創(chuàng)建實(shí)體類

根據(jù)前面設(shè)計(jì)的數(shù)據(jù)庫(kù),我們需要?jiǎng)?chuàng)建三個(gè)實(shí)體類。

首先來創(chuàng)建角色類 Role:

public?class?Role?{
????private?Integer?id;
????private?String?name;
????private?String?nameZh;
???????//省略getter/setter
}

然后創(chuàng)建菜單類 Menu:

public?class?Menu?{
????private?Integer?id;
????private?String?pattern;
????private?List<Role>?roles;
????//省略getter/setter
}

菜單類中包含一個(gè) roles 屬性,表示訪問該項(xiàng)資源所需要的角色。

最后我們創(chuàng)建 User 類:

public?class?User?implements?UserDetails?{
????private?Integer?id;
????private?String?password;
????private?String?username;
????private?boolean?enabled;
????private?boolean?locked;
????private?List<Role>?roles;
????@Override
????public?Collection<??extends?GrantedAuthority>?getAuthorities()?{
????????return?roles.stream()
????????????????????????.map(r?->?new?SimpleGrantedAuthority(r.getName()))
????????????????????????.collect(Collectors.toList());
????}
????@Override
????public?String?getPassword()?{
????????return?password;
????}
????@Override
????public?String?getUsername()?{
????????return?username;
????}
????@Override
????public?boolean?isAccountNonExpired()?{
????????return?true;
????}
????@Override
????public?boolean?isAccountNonLocked()?{
????????return?!locked;
????}
????@Override
????public?boolean?isCredentialsNonExpired()?{
????????return?true;
????}
????@Override
????public?boolean?isEnabled()?{
????????return?enabled;
????}
????//省略其他getter/setter
}

由于數(shù)據(jù)庫(kù)中有 enabled 和 locked 字段,所以 isEnabled() 和 isAccountNonLocked() 兩個(gè)方法如實(shí)返回,其他幾個(gè)賬戶狀態(tài)方法默認(rèn)返回 true 即可。在 getAuthorities() 方法中,我們對(duì) roles 屬性進(jìn)行遍歷,組裝出新的集合對(duì)象返回即可。

創(chuàng)建Service

接下來我們創(chuàng)建 UserService 和 MenuService,并提供相應(yīng)的查詢方法。

先來看 UserService:

@Service
public?class?UserService?implements?UserDetailsService?{
????@Autowired
????UserMapper?userMapper;
????@Override
????public?UserDetails?loadUserByUsername(String?username)?
?????????????????????????????????????????????throws?UsernameNotFoundException?{
????????User?user?=?userMapper.loadUserByUsername(username);
????????if?(user?==?null)?{
????????????throw?new?UsernameNotFoundException("用戶不存在");
????????}
????????user.setRoles(userMapper.getUserRoleByUid(user.getId()));
????????return?user;
????}
}

這段代碼應(yīng)該不用多說了,不熟悉的讀者可以參考本書 2.4 節(jié)。

對(duì)應(yīng)的 UserMapper 如下:

@Mapper
public?interface?UserMapper?{
????List<Role>?getUserRoleByUid(Integer?uid);
????User?loadUserByUsername(String?username);
}

UserMapper.xml:

<!DOCTYPE?mapper
????????PUBLIC?"-//mybatis.org//DTD?Mapper?3.0//EN"
????????"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper?namespace="org.javaboy.base_on_url_dy.mapper.UserMapper">
????<select?id="loadUserByUsername"?
?????????????????????????resultType="org.javaboy.base_on_url_dy.model.User">
????????select?*?from?user?where?username=#{username};
????</select>
????<select?id="getUserRoleByUid"?
?????????????????????????resultType="org.javaboy.base_on_url_dy.model.Role">
????????select?r.*?from?role?r,user_role?ur?where?ur.uid=#{uid}?and?ur.rid=r.id
????</select>
</mapper>

再來看 MenuService,該類只需要提供一個(gè)方法,就是查詢出所有的 Menu 數(shù)據(jù),代碼如下:

@Service
public?class?MenuService?{
????@Autowired
????MenuMapper?menuMapper;
????public?List<Menu>?getAllMenu()?{
????????return?menuMapper.getAllMenu();
????}
}

MenuMapper:

@Mapper
public?interface?MenuMapper?{
????List<Menu>?getAllMenu();
}

MenuMapper.xml:

<!DOCTYPE?mapper
????????PUBLIC?"-//mybatis.org//DTD?Mapper?3.0//EN"
????????"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper?namespace="org.javaboy.base_on_url_dy.mapper.MenuMapper">
????<resultMap?id="MenuResultMap"?
????????????????????????????????type="org.javaboy.base_on_url_dy.model.Menu">
????????<id?property="id"?column="id"/>
????????<result?property="pattern"?column="pattern"></result>
????????<collection?property="roles"?
??????????????????????????????ofType="org.javaboy.base_on_url_dy.model.Role">
????????????<id?column="rid"?property="id"/>
????????????<result?column="rname"?property="name"/>
????????????<result?column="rnameZh"?property="nameZh"/>
????????</collection>
????</resultMap>
????<select?id="getAllMenu"?resultMap="MenuResultMap">
????????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?r.`id`=mr.`rid`
????</select>
</mapper>

需要注意,由于每一個(gè) Menu 對(duì)象都包含了一個(gè) Role 集合,所以這個(gè)查詢是一對(duì)多,這里通過 resultMap 來進(jìn)行查詢結(jié)果映射。

至此,所有基礎(chǔ)工作都完成了,接下來配置 Spring Security。

配置Spring Security

回顧 13.3.6 小節(jié)的內(nèi)容,SecurityMetadataSource 接口負(fù)責(zé)提供受保護(hù)對(duì)象所需要的權(quán)限。在本案例中,受保護(hù)對(duì)象所需要的權(quán)限保存在數(shù)據(jù)庫(kù)中,所以我們可以通過自定義類繼承自 FilterInvocationSecurityMetadataSource,并重寫 getAttributes 方法來提供受保護(hù)對(duì)象所需要的權(quán)限,代碼如下:

@Component
public?class?CustomSecurityMetadataSource?
?????????????????????????implements?FilterInvocationSecurityMetadataSource?{
????@Autowired
????MenuService?menuService;
????AntPathMatcher?antPathMatcher?=?new?AntPathMatcher();

????@Override
????public?Collection<ConfigAttribute>?getAttributes(Object?object)?
???????????????????????????????????????????????throws?IllegalArgumentException?{
????????String?requestURI?=?
???????????????????((FilterInvocation)?object).getRequest().getRequestURI();
????????List<Menu>?allMenu?=?menuService.getAllMenu();
????????for?(Menu?menu?:?allMenu)?{
????????????if?(antPathMatcher.match(menu.getPattern(),?requestURI))?{
????????????????String[]?roles?=?menu.getRoles().stream()
???????????????????????????????.map(r?->?r.getName()).toArray(String[]::new);
????????????????return?SecurityConfig.createList(roles);
????????????}
????????}
????????return?null;
????}

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

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

自定義 CustomSecurityMetadataSource 類并實(shí)現(xiàn) FilterInvocationSecurityMetadataSource 接口,然后重寫它里邊的三個(gè)方法:

  • getAttributes:該方法的參數(shù)是受保護(hù)對(duì)象,在基于 URL 地址的權(quán)限控制中,受保護(hù)對(duì)象就是 FilterInvocation;該方法的返回值則是訪問受保護(hù)對(duì)象所需要的權(quán)限。在該方法里邊,我們首先從受保護(hù)對(duì)象 FilterInvocation 中提取出當(dāng)前請(qǐng)求的 URL 地址,例如 /admin/hello,然后通過 menuService 對(duì)象查詢出所有的菜單數(shù)據(jù)(每條數(shù)據(jù)中都包含訪問該條記錄所需要的權(quán)限),遍歷查詢出來的菜單數(shù)據(jù),如果當(dāng)前請(qǐng)求的 URL 地址和菜單中某一條記錄的 pattern 屬性匹配上了(例如 /admin/hello 匹配上 /admin/**),那么我們就可以獲取當(dāng)前請(qǐng)求所需要的權(quán)限。從 menu 對(duì)象中獲取 roles 屬性,并將其轉(zhuǎn)為一個(gè)數(shù)組,然后通過 SecurityConfig.createList 方法創(chuàng)建一個(gè) Collection<ConfigAttribute> 對(duì)象并返回。如果當(dāng)前請(qǐng)求的 URL 地址和數(shù)據(jù)庫(kù)中 menu 表的所有項(xiàng)都匹配不上,那么最終返回 null。如果返回 null,那么受保護(hù)對(duì)象到底能不能訪問呢?這就要看 AbstractSecurityInterceptor 對(duì)象中的 rejectPublicInvocations 屬性了,該屬性默認(rèn)為 false,表示當(dāng) getAttributes 方法返回 null 時(shí),允許訪問受保護(hù)對(duì)象(回顧 13.4.4 小節(jié)中關(guān)于 AbstractSecurityInterceptor#beforeInvocation 的講解)。
  • getAllConfigAttributes:該方法可以用來返回所有的權(quán)限屬性,以便在項(xiàng)目啟動(dòng)階段做校驗(yàn),如果不需要校驗(yàn),則直接返回 null 即可。
  • supports:該方法表示當(dāng)前對(duì)象支持處理的受保護(hù)對(duì)象是 FilterInvocation。

CustomSecurityMetadataSource 類配置完成后,接下來我們要用它來代替默認(rèn)的 SecurityMetadataSource 對(duì)象,具體配置如下:

@Configuration
public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{
????@Autowired
????CustomSecurityMetadataSource?customSecurityMetadataSource;
????@Autowired
????UserService?userService;

????@Override
????protected?void?configure(AuthenticationManagerBuilder?auth)?
????????????????????????????????????????????????????????????????throws?Exception?{
????????auth.userDetailsService(userService);
????}

????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????ApplicationContext?applicationContext?=?
??????????????????????????????http.getSharedObject(ApplicationContext.class);
????????http.apply(new?UrlAuthorizationConfigurer<>(applicationContext))
????????????????.withObjectPostProcessor(new?
???????????????????????????ObjectPostProcessor<FilterSecurityInterceptor>()?{
????????????????????@Override
????????????????????public?<O?extends?FilterSecurityInterceptor>?O?
????????????????????????????????????????????????????????????postProcess(O?object)?{
???????????object.setSecurityMetadataSource(customSecurityMetadataSource);
????????????????????????return?object;
????????????????????}
????????????????});
????????http.formLogin()
????????????????.and()
????????????????.csrf().disable();
????}
}

關(guān)于用戶的配置無需多說,我們重點(diǎn)來看 configure(HttpSecurity) 方法。

由于訪問路徑規(guī)則和所需要的權(quán)限之間的映射關(guān)系已經(jīng)保存在數(shù)據(jù)庫(kù)中,所以我們就沒有必要在 Java 代碼中配置映射關(guān)系了,同時(shí)這里的權(quán)限對(duì)比也不會(huì)用到權(quán)限表達(dá)式,所以我們通過 UrlAuthorizationConfigurer 來進(jìn)行配置。

在配置的過程中,通過 withObjectPostProcessor 方法調(diào)用 ObjectPostProcessor 對(duì)象后置處理器,在對(duì)象后置處理器中,將 FilterSecurityInterceptor 中的 SecurityMetadataSource 對(duì)象替換為我們自定義的 customSecurityMetadataSource 對(duì)象即可。

2. 測(cè)試

接下來創(chuàng)建 HelloController,代碼如下:

@RestController
public?class?HelloController?{
????@GetMapping("/admin/hello")
????public?String?admin()?{
????????return?"hello?admin";
????}
????@GetMapping("/user/hello")
????public?String?user()?{
????????return?"hello?user";
????}
????@GetMapping("/guest/hello")
????public?String?guest()?{
????????return?"hello?guest";
????}
????@GetMapping("/hello")
????public?String?hello()?{
????????return?"hello";
????}
}

最后啟動(dòng)項(xiàng)目進(jìn)行測(cè)試。

首先使用 admin/123 進(jìn)行登錄,該用戶具備 ROLE_ADMIN 角色,ROLE_ADMIN 可以訪問 /admin/hello、/user/hello 以及 /guest/hello 三個(gè)接口。

接下來使用 user/123 進(jìn)行登錄,該用戶具備 ROLE_USER 角色,ROLE_USER 可以訪問 /user/hello 以及 /guest/hello 兩個(gè)接口。

最后使用 javaboy/123 進(jìn)行登錄,該用戶具備 ROLE_GUEST 角色,ROLE_GUEST 可以訪問 /guest/hello 接口。

由于 /hello 接口不包含在 URL-權(quán)限 映射關(guān)系中,所以任何用戶都可以訪問 /hello 接口,包括匿名用戶。如果希望所有的 URL 地址都必須在數(shù)據(jù)庫(kù)中配置 URL-權(quán)限 映射關(guān)系后才能訪問,那么可以通過如下配置實(shí)現(xiàn):

http.apply(new?UrlAuthorizationConfigurer<>(applicationContext))
????????.withObjectPostProcessor(new??
???????????????????????????ObjectPostProcessor<FilterSecurityInterceptor>()?{
????????????@Override
????????????public?<O?extends?FilterSecurityInterceptor>?O?
???????????????????????????????????????????????????????????postProcess(O?object)?{???
???????????object.setSecurityMetadataSource(customSecurityMetadataSource);
????????????????object.setRejectPublicInvocations(true);
????????????????return?object;
????????????}
????????});

通過設(shè)置 FilterSecurityInterceptor 中的 rejectPublicInvocations 屬性為 true,就可以關(guān)閉URL的公開訪問,所有 URL 必須具備對(duì)應(yīng)的權(quán)限才能訪問。

以上就是Spring Security動(dòng)態(tài)權(quán)限的實(shí)現(xiàn)方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Security動(dòng)態(tài)權(quán)限的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 關(guān)于MyBatis模糊查詢的幾種實(shí)現(xiàn)方式

    關(guān)于MyBatis模糊查詢的幾種實(shí)現(xiàn)方式

    在實(shí)際項(xiàng)目中,我們會(huì)經(jīng)常對(duì)數(shù)據(jù)做一些模糊查詢的操作,這時(shí)候就需要利用到 like字段,那么在Mybatis中,有哪些方式可以實(shí)現(xiàn)模糊查詢呢,需要的朋友可以參考下
    2023-05-05
  • Java中的Gradle與Groovy的區(qū)別及存在的關(guān)系

    Java中的Gradle與Groovy的區(qū)別及存在的關(guān)系

    這篇文章主要介紹了Java中的Gradle與Groovy的區(qū)別及存在的關(guān)系,Groovy是一種JVM語言,它可以編譯為與Java相同的字節(jié)碼,并且可以與Java類無縫地互操作,Gradle是Java項(xiàng)目中主要的構(gòu)建系統(tǒng)之一,下文關(guān)于兩者的詳細(xì)內(nèi)容,需要的小伙伴可以參考一下
    2022-02-02
  • JDBC核心技術(shù)詳解

    JDBC核心技術(shù)詳解

    這篇文章主要介紹了JDBC核心技術(shù)詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)JDBC的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05
  • java實(shí)現(xiàn)題目以及選項(xiàng)亂序的方法實(shí)例

    java實(shí)現(xiàn)題目以及選項(xiàng)亂序的方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于java實(shí)現(xiàn)題目以及選項(xiàng)亂序的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Java 獲取上一個(gè)月的月份的正確寫法

    Java 獲取上一個(gè)月的月份的正確寫法

    這篇文章主要介紹了java獲取上一個(gè)月月份,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • 詳解 Corba開發(fā)之Java實(shí)現(xiàn)Service與Client

    詳解 Corba開發(fā)之Java實(shí)現(xiàn)Service與Client

    這篇文章主要介紹了詳解 Corba開發(fā)之Java實(shí)現(xiàn)Service與Client的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • Java中的布隆過濾器原理實(shí)現(xiàn)和應(yīng)用

    Java中的布隆過濾器原理實(shí)現(xiàn)和應(yīng)用

    Java中的布隆過濾器是一種基于哈希函數(shù)的數(shù)據(jù)結(jié)構(gòu),能夠高效地判斷元素是否存在于一個(gè)集合中。它廣泛應(yīng)用于緩存、網(wǎng)絡(luò)協(xié)議、數(shù)據(jù)查詢等領(lǐng)域,在提高程序性能和減少資源消耗方面具有顯著優(yōu)勢(shì)
    2023-04-04
  • SpringBoot全局異常與數(shù)據(jù)校驗(yàn)的方法

    SpringBoot全局異常與數(shù)據(jù)校驗(yàn)的方法

    這篇文章主要介紹了SpringBoot全局異常與數(shù)據(jù)校驗(yàn)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • SpringBoot+Redis實(shí)現(xiàn)數(shù)據(jù)字典的方法

    SpringBoot+Redis實(shí)現(xiàn)數(shù)據(jù)字典的方法

    這篇文章主要介紹了SpringBoot+Redis實(shí)現(xiàn)數(shù)據(jù)字典的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • springboot?aop配合反射統(tǒng)一簽名驗(yàn)證實(shí)踐

    springboot?aop配合反射統(tǒng)一簽名驗(yàn)證實(shí)踐

    這篇文章主要介紹了springboot?aop配合反射統(tǒng)一簽名驗(yàn)證實(shí)踐,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評(píng)論