最近写了几个Spring Boot组件,项目用什么功能就引入对应的依赖,配置配置就能使用,香的很!那么Spring Security能不能也弄成模块化,简单配置一下就可以用上呢?JWT得有,RBAC动态权限更得有!花了小半天就写了个组件,用了一个月感觉还不错。是我一个人爽?还是放出来让大家一起爽?经过我翻来覆去的思想斗争了一个月,最后做出了一个明智的决定,放出来让想直接上手的同学直接使用。源码地址就在下面:
https://gitee.com/felord/security-enhance-spring-boot
用法
集成
这就是一个Spring Boot Starter,你自己打包、安装。然后引用到项目:
- <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会被缓存到以usrTkn为key的缓存中,如果你想定制的话,自行实现一个JwtTokenStorage并注入Spring IoC就可以覆盖下面的配置了:
- @Bean
- @ConditionalOnMissingBean
- public JwtTokenStorage jwtTokenStorage() {
- return new SpringCacheJwtTokenStorage();
- }
你应该去了解如何自定义Spring Cache的过期时间。
数据库表设计
然后是数据库表设计,这里简单点弄个RBAC的设计,仅供参考,你可以根据你们的业务改良。
用户表:
user_id | username | password |
---|---|---|
1312434534 | felord | {noop}12345 |
角色表:
role_id | role_name | role_code |
---|---|---|
12343667867 | 管理员 | ADMIN |
用户角色关联表:
user_role_id | user_id | role_id |
---|---|---|
12354657777 | 1312434534 | 12343667867 |
一个用户可以持有多个角色,一个角色在一个用户持有的角色集合中是唯一的。
资源表:
resources_id | resources_name | resource_pattern | method |
---|---|---|---|
12543667867 | 根据ID获取商品 | /goods/{goodsId} | GET |
资源其实就是我们写的Spring MVC接口,这里支持ANT风格,但是尽量具体,为了灵活性考虑不推荐使用通配符。
角色资源表:
role_res_id | role_id | resources_id |
---|---|---|
4545466445 | 12343667867 | 12543667867 |
一个资源可以关联多个角色,一个角色不能重复持有一个资源。
实现UserDetailsService
实现用户加载服务接口UserDetailsService是Spring Security开发的必要步骤,跟我以前的教程差不多。
- @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);
- }
这里要说一下里面为啥要内置一个ANONYMOUS角色给用户。如果希望特定的资源对用户全量开放,可配置对应的权限角色编码为ANONYMOUS。当某个资源的角色编码为ANONYMOUS时,即使不携带Token也可以访问。一般情况下匿名能访问的资源不匿名一定能访问,当然你如果不希望这样的规则存在干掉就是了。
查询用户的权限集
实现用户角色权限方法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
- # 过期秒数
- expires-at: 604800
最后别忘记弄个配置类并标记@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
然后会返回一对JWT,返回包含两个token主体
- accessToken 用来日常进行请求鉴权,有过期时间。
- refreshToken 当accessToken过期失效时,用来刷新accessToken。
结构为:
- {
- "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": {}
- }
调用根据ID获取商品接口时加入Token:
- GET /goods/234355451 HTTP/1.1
- Host: localhost:8080
- Authorization: Bearer eyJraWQImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ1NzgsImlhdCI6MTYxNjkxODk3OCwianRpIjoiNThlOTQktNGVlYzc3MDU0ZDk3In0.ZQcN0FX7_taohqPiC1KnoF7
本文转载自微信公众号「码农小胖哥」,可以通过以下二维码关注。转载本文请联系码农小胖哥公众号。