經(jīng)過(guò)我翻來(lái)覆去的思想斗爭(zhēng)了一個(gè)月,最后做出了一個(gè)明智的決定
最近寫了幾個(gè)Spring Boot組件,項(xiàng)目用什么功能就引入對(duì)應(yīng)的依賴,配置配置就能使用,香的很!那么Spring Security能不能也弄成模塊化,簡(jiǎn)單配置一下就可以用上呢?JWT得有,RBAC動(dòng)態(tài)權(quán)限更得有!花了小半天就寫了個(gè)組件,用了一個(gè)月感覺(jué)還不錯(cuò)。是我一個(gè)人爽?還是放出來(lái)讓大家一起爽?經(jīng)過(guò)我翻來(lái)覆去的思想斗爭(zhēng)了一個(gè)月,最后做出了一個(gè)明智的決定,放出來(lái)讓想直接上手的同學(xué)直接使用。源碼地址就在下面:
https://gitee.com/felord/security-enhance-spring-boot
用法
集成
這就是一個(gè)Spring Boot Starter,你自己打包、安裝。然后引用到項(xiàng)目:
- <dependency>
- <groupId>cn.felord.security</groupId>
- <artifactId>security-enhance-spring-boot-starter</artifactId>
- <version>${version}</version>
- </dependency>
另外你需要集成Spring Cache,比如Redis Cache:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-cache</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- </dependency>
JWT會(huì)被緩存到以u(píng)srTkn為key的緩存中,如果你想定制的話,自行實(shí)現(xiàn)一個(gè)JwtTokenStorage并注入Spring IoC就可以覆蓋下面的配置了:
- @Bean
- @ConditionalOnMissingBean
- public JwtTokenStorage jwtTokenStorage() {
- return new SpringCacheJwtTokenStorage();
- }
你應(yīng)該去了解如何自定義Spring Cache的過(guò)期時(shí)間。
數(shù)據(jù)庫(kù)表設(shè)計(jì)
然后是數(shù)據(jù)庫(kù)表設(shè)計(jì),這里簡(jiǎn)單點(diǎn)弄個(gè)RBAC的設(shè)計(jì),僅供參考,你可以根據(jù)你們的業(yè)務(wù)改良。
用戶表:
user_id | username | password |
---|---|---|
1312434534 | felord | {noop}12345 |
角色表:
role_id | role_name | role_code |
---|---|---|
12343667867 | 管理員 | ADMIN |
用戶角色關(guān)聯(lián)表:
user_role_id | user_id | role_id |
---|---|---|
12354657777 | 1312434534 | 12343667867 |
一個(gè)用戶可以持有多個(gè)角色,一個(gè)角色在一個(gè)用戶持有的角色集合中是唯一的。
資源表:
resources_id | resources_name | resource_pattern | method |
---|---|---|---|
12543667867 | 根據(jù)ID獲取商品 | /goods/{goodsId} | GET |
資源其實(shí)就是我們寫的Spring MVC接口,這里支持ANT風(fēng)格,但是盡量具體,為了靈活性考慮不推薦使用通配符。
角色資源表:
role_res_id | role_id | resources_id |
---|---|---|
4545466445 | 12343667867 | 12543667867 |
一個(gè)資源可以關(guān)聯(lián)多個(gè)角色,一個(gè)角色不能重復(fù)持有一個(gè)資源。
實(shí)現(xiàn)UserDetailsService
實(shí)現(xiàn)用戶加載服務(wù)接口UserDetailsService是Spring Security開發(fā)的必要步驟,跟我以前的教程差不多。
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- UserInfo userInfo = this.lambdaQuery()
- .eq(UserInfo::getUsername, username).one();
- if (Objects.isNull(userInfo)) {
- throw new UsernameNotFoundException("用戶:" + username + " 不存在");
- }
- String userId = userInfo.getUserId();
- boolean enabled = userInfo.getEnabled();
- Set<String> roles = iUserRoleService.getRolesByUserId(userId);
- roles.add(“"ANONYMOUS"”);
- Set<GrantedAuthority> roleSet = roles.stream()
- .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
- .collect(Collectors.toSet());
- return new SecureUser(userId,
- username,
- userInfo.getSecret(),
- enabled,
- enabled,
- enabled,
- enabled,
- roleSet);
- }
這里要說(shuō)一下里面為啥要內(nèi)置一個(gè)ANONYMOUS角色給用戶。如果希望特定的資源對(duì)用戶全量開放,可配置對(duì)應(yīng)的權(quán)限角色編碼為ANONYMOUS。當(dāng)某個(gè)資源的角色編碼為ANONYMOUS時(shí),即使不攜帶Token也可以訪問(wèn)。一般情況下匿名能訪問(wèn)的資源不匿名一定能訪問(wèn),當(dāng)然你如果不希望這樣的規(guī)則存在干掉就是了。
查詢用戶的權(quán)限集
實(shí)現(xiàn)用戶角色權(quán)限方法Function
配置
最后就是配置了,跟我以前教程中的配置幾乎一樣,application.yaml的配置為:
- # jwt 配置
- jwt:
- cert-info:
- # keytool 密鑰的 alias
- alias: felord
- # 密匙密碼
- key-password: i6x123akg15v13
- # 路徑 這里是在resources 包下
- cert-location: jwt.jks
- claims:
- # jwt iss 字段值
- issuer: https://felord.cn
- # sub 字段
- subject: all
- # 過(guò)期秒數(shù)
- expires-at: 604800
最后別忘記弄個(gè)配置類并標(biāo)記@EnableSpringSecurity以啟用配置:
- @EnableSpringSecurity
- @Configuration(proxyBeanMethods = false)
- public class SecurityConfiguration {
- /**
- * Function function.
- *
- * @param resourcesService the resources service
- * @return the function
- */
- @Bean
- Function<Set<String>, Set<AntPathRequestMatcher>> function(IResourcesService resourcesService){
- return resourcesService::matchers;
- }
- @Bean
- UserDetailsService userDetailsService(IUserInfoService userInfoService){
- return userInfoService::loadUserByUsername;
- }
- }
記得使用@EnableCaching開啟并配置緩存。
使用
登錄接口
- POST /login?username=felord&password=12345 HTTP/1.1
- Host: localhost:8080
然后會(huì)返回一對(duì)JWT,返回包含兩個(gè)token主體
- accessToken 用來(lái)日常進(jìn)行請(qǐng)求鑒權(quán),有過(guò)期時(shí)間。
- refreshToken 當(dāng)accessToken過(guò)期失效時(shí),用來(lái)刷新accessToken。
結(jié)構(gòu)為:
- {
- "accessToken": {
- "tokenValue": "",
- "issuedAt": {
- "epochSecond": 1616827822,
- "nano": 393000000
- },
- "expiresAt": {
- "epochSecond": 1616831422,
- "nano": 393000000
- },
- "tokenType": {
- "value": "Bearer"
- },
- "scopes": [
- "ROLE_ADMIN",
- "ROLE_ANONYMOUS"
- ]
- },
- "refreshToken": {
- "tokenValue": "",
- "issuedAt": {
- "epochSecond": 1616827822,
- "nano": 393000000
- },
- "expiresAt": null
- },
- "additionalParameters": {}
- }
調(diào)用根據(jù)ID獲取商品接口時(shí)加入Token:
- GET /goods/234355451 HTTP/1.1
- Host: localhost:8080
- Authorization: Bearer eyJraWQImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ1NzgsImlhdCI6MTYxNjkxODk3OCwianRpIjoiNThlOTQktNGVlYzc3MDU0ZDk3In0.ZQcN0FX7_taohqPiC1KnoF7
本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)小胖哥」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)小胖哥公眾號(hào)。