Skip to content

Commit 5635e41

Browse files
committed
feat: 添加 jwt 和 redis 两种 token 校验模式
1 parent 63d7b1d commit 5635e41

16 files changed

Lines changed: 374 additions & 87 deletions

File tree

simple-application/src/main/resources/application.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ sra:
3737
config:
3838
dynamic-ds-on: true
3939
swagger-on: true
40-
security-on: true
4140
# 动态数据源配置
4241
dynamic-ds:
4342
master: db1
@@ -67,7 +66,7 @@ sra:
6766
test: top.cadecode.sra.test
6867
# security 配置
6968
security:
70-
auth-model: redis
69+
auth-model: jwt
7170
token:
7271
header: token
7372
expiration: 86400
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package top.cadecode.sra.common.consts;
2+
3+
/**
4+
* @author Cade Li
5+
* @date 2022/5/29
6+
* @description Redis key 命名前缀
7+
*/
8+
public class RedisKeyPrefix {
9+
10+
/**
11+
* 登录用户信息
12+
*/
13+
public static final String LOGIN_USER = "sra:login:user";
14+
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package top.cadecode.sra.common.datasource;
2+
3+
/**
4+
* @author Cade Li
5+
* @date 2022/5/29
6+
* @description Redis key 命名生成器
7+
*/
8+
public class RedisKeyGenerator {
9+
10+
/**
11+
* 生成 : 分割的 redis key
12+
*
13+
* @param prefix 前缀
14+
* @param extra 其他字符串
15+
* @return redis key
16+
*/
17+
public static String key(String prefix, String... extra) {
18+
StringBuilder prefixBuilder = new StringBuilder(prefix);
19+
for (String str : extra) {
20+
prefixBuilder.append(":").append(str);
21+
}
22+
return prefixBuilder.toString();
23+
}
24+
25+
}

simple-framework/src/main/java/top/cadecode/sra/framework/config/core/SRAMainConfig.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,4 @@ public class SRAMainConfig {
3232
* 是否开启 swagger 文档
3333
*/
3434
private boolean swaggerOn;
35-
36-
/**
37-
* 是否开启 spring security
38-
*/
39-
private boolean securityOn;
40-
4135
}

simple-framework/src/main/java/top/cadecode/sra/framework/config/core/SecurityConfig.java

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,30 @@
1111
import org.springframework.context.annotation.Configuration;
1212
import org.springframework.http.HttpMethod;
1313
import org.springframework.security.access.vote.UnanimousBased;
14+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
15+
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
1416
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1517
import org.springframework.security.config.annotation.web.builders.WebSecurity;
1618
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
1719
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1820
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
1921
import org.springframework.security.config.http.SessionCreationPolicy;
22+
import org.springframework.security.core.userdetails.UserDetailsService;
2023
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
2124
import org.springframework.security.crypto.password.PasswordEncoder;
2225
import org.springframework.security.web.access.expression.WebExpressionVoter;
2326
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2427
import top.cadecode.sra.common.enums.AuthModelEnum;
28+
import top.cadecode.sra.framework.security.LoginSuccessHandler;
2529
import top.cadecode.sra.framework.security.TokenAuthFilter;
26-
import top.cadecode.sra.framework.security.handler.*;
30+
import top.cadecode.sra.framework.security.handler.LoginFailureHandler;
31+
import top.cadecode.sra.framework.security.handler.NoAuthenticationHandler;
32+
import top.cadecode.sra.framework.security.handler.NoAuthorityHandler;
33+
import top.cadecode.sra.framework.security.handler.SignOutSuccessHandler;
34+
import top.cadecode.sra.framework.security.voter.DataBaseRoleVoter;
2735

2836
import java.util.Arrays;
2937
import java.util.List;
30-
import java.util.Objects;
31-
import java.util.stream.Collectors;
3238

3339
/**
3440
* @author Cade Li
@@ -39,9 +45,9 @@
3945
@Data
4046
@RequiredArgsConstructor
4147
@EnableWebSecurity
48+
@EnableGlobalMethodSecurity(prePostEnabled = true)
4249
@Configuration
4350
@ConfigurationProperties("sra.security")
44-
@ConditionalOnProperty(name = "sra.config.security-on", havingValue = "true")
4551
public class SecurityConfig {
4652

4753
/**
@@ -76,14 +82,38 @@ public class SecurityConfig {
7682
/**
7783
* 注入 Token 过滤器
7884
*/
79-
private final List<TokenAuthFilter> tokenAuthFilters;
85+
private final TokenAuthFilter tokenAuthFilter;
8086

8187
/**
82-
* security 配置
88+
* 注入投票器
89+
*/
90+
private final DataBaseRoleVoter dataBaseRoleVoter;
91+
92+
/**
93+
* 注入 UserDetailsService
94+
*/
95+
private final UserDetailsService userDetailsService;
96+
97+
/**
98+
* 密码加密器
99+
*/
100+
@Bean
101+
public PasswordEncoder passwordEncoder() {
102+
return new BCryptPasswordEncoder();
103+
}
104+
105+
/**
106+
* Security 配置
83107
*/
84108
@Bean
85-
public WebSecurityConfigurerAdapter webSecurityConfigurer() {
109+
public WebSecurityConfigurerAdapter webSecurityConfigurer(PasswordEncoder passwordEncoder) {
86110
return new WebSecurityConfigurerAdapter() {
111+
112+
@Override
113+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
114+
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
115+
}
116+
87117
@Override
88118
protected void configure(HttpSecurity http) throws Exception {
89119
// 关闭 csrf
@@ -95,11 +125,6 @@ protected void configure(HttpSecurity http) throws Exception {
95125
// 尝试请求直接通过
96126
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
97127
.anyRequest().authenticated();
98-
// 配置登录处理器
99-
http.formLogin().permitAll()
100-
.loginProcessingUrl(LOGIN_URL)
101-
.successHandler(loginSuccessHandler)
102-
.failureHandler(loginFailureHandler);
103128
// 配置注销处理器
104129
http.logout().permitAll()
105130
.logoutUrl(LOGOUT_URL)
@@ -109,24 +134,18 @@ protected void configure(HttpSecurity http) throws Exception {
109134
// 配置未登录处理器
110135
.authenticationEntryPoint(noAuthenticationHandler)
111136
// 配置无权限处理器
112-
.accessDeniedHandler(noAuthorityHandler)
113-
.and()
114-
// 自定义的 accessDecisionManager
115-
.authorizeRequests()
137+
.accessDeniedHandler(noAuthorityHandler);
138+
// 自定义的 accessDecisionManager
139+
http.authorizeRequests()
116140
.accessDecisionManager(new UnanimousBased(Arrays.asList(new WebExpressionVoter())));
117-
// 根据认证模式配置过滤器
118-
if (Objects.isNull(authModel)) {
119-
authModel = AuthModelEnum.JWT;
120-
log.info("没有配置认证模式,默认为 {}", authModel);
121-
}
141+
// 配置登录处理器
142+
http.formLogin().permitAll()
143+
.loginProcessingUrl(LOGIN_URL)
144+
.successHandler(loginSuccessHandler)
145+
.failureHandler(loginFailureHandler);
122146
// 配置 Token 校验过滤器
123-
List<TokenAuthFilter> filters = tokenAuthFilters.stream()
124-
.filter(o -> o.getAuthModel() == authModel)
125-
.collect(Collectors.toList());
126-
if (!filters.isEmpty()) {
127-
http.addFilterBefore(filters.get(0), UsernamePasswordAuthenticationFilter.class);
128-
log.info("完成 Security 配置,认证模式 {}", authModel);
129-
}
147+
http.addFilterBefore(tokenAuthFilter, UsernamePasswordAuthenticationFilter.class);
148+
log.info("完成 Security 配置,认证模式 {}", authModel);
130149
}
131150

132151
@Override
@@ -143,13 +162,4 @@ public void configure(WebSecurity web) {
143162
}
144163
};
145164
}
146-
147-
/**
148-
* 密码加密器
149-
*/
150-
@Bean
151-
public PasswordEncoder passwordEncoder() {
152-
return new BCryptPasswordEncoder();
153-
}
154-
155165
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package top.cadecode.sra.framework.security;
2+
3+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
4+
import top.cadecode.sra.common.enums.AuthModelEnum;
5+
6+
/**
7+
* @author Cade Li
8+
* @date 2022/5/28
9+
* @description 登录成功处理器抽象
10+
*/
11+
public abstract class LoginSuccessHandler implements AuthenticationSuccessHandler {
12+
13+
/**
14+
* 返回当前模式,用于策略模式
15+
*/
16+
public abstract AuthModelEnum getAuthModel();
17+
}

simple-framework/src/main/java/top/cadecode/sra/framework/security/TokenAuthFilter.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
package top.cadecode.sra.framework.security;
22

3+
import cn.hutool.core.util.CharsetUtil;
4+
import cn.hutool.extra.servlet.ServletUtil;
5+
import cn.hutool.http.ContentType;
6+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
7+
import org.springframework.security.core.context.SecurityContextHolder;
8+
import org.springframework.security.core.userdetails.UserDetails;
9+
import org.springframework.security.web.authentication.WebAuthenticationDetails;
310
import org.springframework.web.filter.OncePerRequestFilter;
411
import top.cadecode.sra.common.enums.AuthModelEnum;
12+
import top.cadecode.sra.common.exception.ApiErrorCode;
13+
import top.cadecode.sra.common.response.ApiResult;
14+
import top.cadecode.sra.common.response.ApiStatus;
15+
import top.cadecode.sra.framework.util.JacksonUtil;
16+
17+
import javax.servlet.http.HttpServletRequest;
18+
import javax.servlet.http.HttpServletResponse;
519

620
/**
721
* @author Cade Li
@@ -14,4 +28,31 @@ public abstract class TokenAuthFilter extends OncePerRequestFilter {
1428
* 返回当前模式,用于策略模式
1529
*/
1630
public abstract AuthModelEnum getAuthModel();
31+
32+
/**
33+
* 设置认证信息
34+
*
35+
* @param request 请求对象
36+
* @param userDetails 用户信息
37+
*/
38+
protected void setAuthentication(HttpServletRequest request, UserDetails userDetails) {
39+
// 构造 UsernamePasswordAuthenticationToken
40+
UsernamePasswordAuthenticationToken authentication =
41+
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
42+
// 设置认证信息,用于后面的过滤器使用
43+
authentication.setDetails(new WebAuthenticationDetails(request));
44+
SecurityContextHolder.getContext().setAuthentication(authentication);
45+
}
46+
47+
/**
48+
* 将错误信息写入响应
49+
*
50+
* @param response 响应对象
51+
* @param errorCode 错误信息枚举类
52+
* @param requestURI 请求路径
53+
*/
54+
protected void writeResponse(HttpServletResponse response, ApiErrorCode errorCode, String requestURI) {
55+
ApiResult<Object> result = ApiResult.error(errorCode).status(ApiStatus.NO_AUTHENTICATION).path(requestURI);
56+
ServletUtil.write(response, JacksonUtil.toJson(result), ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8));
57+
}
1758
}

simple-framework/src/main/java/top/cadecode/sra/framework/security/filter/JwtTokenAuthFilter.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package top.cadecode.sra.framework.security.filter;
22

3+
import cn.hutool.core.util.StrUtil;
4+
import cn.hutool.json.JSONObject;
35
import lombok.RequiredArgsConstructor;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
47
import org.springframework.stereotype.Component;
58
import top.cadecode.sra.common.enums.AuthModelEnum;
6-
import top.cadecode.sra.framework.security.JwtTokenHolder;
9+
import top.cadecode.sra.common.enums.error.AuthErrorEnum;
710
import top.cadecode.sra.framework.security.TokenAuthFilter;
11+
import top.cadecode.sra.framework.security.TokenAuthHolder;
12+
import top.cadecode.sra.system.bean.dto.SysUserDto;
813

914
import javax.servlet.FilterChain;
1015
import javax.servlet.ServletException;
@@ -19,9 +24,10 @@
1924
*/
2025
@RequiredArgsConstructor
2126
@Component
27+
@ConditionalOnProperty(name = "sra.security.auth-model", havingValue = "jwt")
2228
public class JwtTokenAuthFilter extends TokenAuthFilter {
2329

24-
private final JwtTokenHolder jwtTokenHolder;
30+
private final TokenAuthHolder tokenAuthHolder;
2531

2632
@Override
2733
public AuthModelEnum getAuthModel() {
@@ -30,6 +36,40 @@ public AuthModelEnum getAuthModel() {
3036

3137
@Override
3238
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
33-
39+
String requestURI = request.getRequestURI();
40+
String jwtToken = request.getHeader(tokenAuthHolder.getHeader());
41+
// token 不存在
42+
if (StrUtil.isEmpty(jwtToken)) {
43+
filterChain.doFilter(request, response);
44+
return;
45+
}
46+
// token 不合法
47+
if (!tokenAuthHolder.verifyToken(jwtToken)) {
48+
writeResponse(response, AuthErrorEnum.TOKEN_ERROR, requestURI);
49+
return;
50+
}
51+
// token 已过期
52+
if (tokenAuthHolder.isExpired(jwtToken)) {
53+
writeResponse(response, AuthErrorEnum.TOKEN_EXPIRED, requestURI);
54+
return;
55+
}
56+
// 获取 jwt 内容
57+
JSONObject payload = tokenAuthHolder.getPayload(jwtToken);
58+
SysUserDto sysUserDto = payload.toBean(SysUserDto.class);
59+
// 设置 AuthenticationToken
60+
setAuthentication(request, sysUserDto);
61+
// 判断是否需要刷新 token
62+
// 获取过期时间,单位秒
63+
int expiresAt = (int) payload.get("exp");
64+
// 过期时间的一半,秒转为毫秒
65+
long halfExpiration = tokenAuthHolder.getExpiration() / 2;
66+
// 如果当时时间距离过期时间不到配置的 expiration 一半,就下发新的 token
67+
if (expiresAt - System.currentTimeMillis() / 1000 < halfExpiration) {
68+
// 生成 jwt token
69+
String newJwtToken = tokenAuthHolder.generateToken(sysUserDto.getId(), sysUserDto.getUsername(), sysUserDto.getRoles());
70+
// token 放在请求头
71+
response.addHeader(tokenAuthHolder.getHeader(), newJwtToken);
72+
}
73+
filterChain.doFilter(request, response);
3474
}
3575
}

0 commit comments

Comments
 (0)