前后端分离

Cros跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.pattern.PathPatternParser;

/**
* Created by XuanRan on 2022/10/3
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
.allowedOrigins("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("*")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}

}

R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import dev.xuanran.springsecurity.code.ErrorCodeEnum;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.HashMap;
import java.util.Map;

/**
* Created by XuanRan on 2022/8/14
*/
@Getter
@Accessors(chain = true)
@NoArgsConstructor
public class R {
public static Integer SUCCESS = 20000;
public static Integer ERROR = 20001;

public static final String DATA_FLAG = "data";

@Setter
private Boolean success;

@Setter
private Integer code;

@Setter
private String message;

@Setter
private Map<String, Object> data = new HashMap<>();

public static R ok() {
R r = new R();
r.setCode(R.SUCCESS);
r.setSuccess(true);
r.setMessage("success");
return r;
}

public static R ok(Integer code, String message) {
R r = new R();
r.setCode(code);
r.setSuccess(true);
r.setMessage(message);
return r;
}

public static R error() {
R r = new R();
r.setCode(R.ERROR);
r.setSuccess(false);
r.setMessage("error");
return r;
}

public static R error(Integer code, String message) {
R r = new R();
r.setCode(code);
r.setSuccess(false);
r.setMessage(message);
return r;
}

public static R error(ErrorCodeEnum codeEnum) {
R r = new R();
r.setCode(codeEnum.getCode());
r.setSuccess(false);
r.setMessage(codeEnum.getMsg());
return r;
}

public R success(Boolean bool) {
this.setSuccess(bool);
return this;
}

public R message(String mes) {
this.setMessage(mes);
return this;
}

public R code(Integer code) {
this.setCode(code);
return this;
}

public R data(String key, Object value) {
this.data.put(key, value);
return this;
}

public R data(Map<String, Object> map) {
this.setData(map);
return this;
}

/**
* 使用setData可避免 R.ok().data("data",obj); 形式数据出现
* 可直接使用 R.ok().setData(obj);
*
* @param obj 存储的数据
* @return this
*/
public R data(Object obj) {
this.data(DATA_FLAG, obj);
return this;
}
}


ResponseUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.alibaba.fastjson.JSON;
import dev.xuanran.springsecurity.exception.custom.ResponseFormatJsonException;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Created by XuanRan on 2022/10/3
* 将字符串渲染到客户端
*/
public class ResponseUtil {
/**
* 将字符串渲染到客户端
* @param response response
* @param r R对象
*/
public static void writeJson(HttpServletResponse response, R r) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(JSON.toJSON(r));
} catch (IOException e) {
e.printStackTrace();
throw new ResponseFormatJsonException();
}
}
}

Mybatis Plus

配置文件

1
2
3
4
5
6
7
8
9
10
11
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: assign_id
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
type-aliases-package: dev.xuanran

分页插件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Created by XuanRan on 2022/10/2
*/
@Configuration
public class MybatisConfiguration {

/**
* Mybatis Plus 分页插件
* 使用:
* Page<T> page = new Page(current,size);
* mapper.selectPage(page,queryWrapper);
*
* @return object
*/
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
return new PaginationInnerInterceptor();
}
}

自动填充字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
* Created by XuanRan on 2022/10/2
* 当Mybatis进行数据插入和更新时执行
*/
@Slf4j
@Component
public class MybatisPlusTableFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 逻辑删除
setFieldValByName("deleted", 0, metaObject);
// 创建时间
setFieldValByName("createTime", new Date(), metaObject);
// 更新时间
setFieldValByName("updateTime", new Date(), metaObject);
// 可见性 0 可见 1 隐藏
setFieldValByName("visible", "0", metaObject);
// 状态 0 可见 1 隐藏
setFieldValByName("status", "0", metaObject);

}

@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("updateTime", new Date(), metaObject);
}
}

Redis

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: spring

jackson:
date-format: yyyy-MM-dd HH:mm:ss # 设置日期格式化

cache:
redis:
cache-null-values: true # 设置空值保存,防止缓存穿透
use-key-prefix: true # 开启缓存前缀
key-prefix: cache # 配置缓存前缀
time-to-live: 100000
type: redis

RedisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import dev.xuanran.springsecurity.util.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);

// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);

template.afterPropertiesSet();
return template;
}
}

FastJsonRedisSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
* Redis使用FastJson序列化
*
* @author sg
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{

public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

private final Class<T> clazz;

static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}

public FastJsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}

@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}

@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);

return JSON.parseObject(str, clazz);
}


protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}

RedisCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key, Class<T> clazz) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
Object jsonObject = operation.get(key);
try {
return JSON.parseObject(JSON.toJSONString(jsonObject), clazz);
} catch (Exception e) {
e.printStackTrace();
if (null != jsonObject) {
return (T) jsonObject;
} else {
return null;
}
}
}

/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}

/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}

/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}

/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}

/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}

/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}

/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}

/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}

/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}

/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}

/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}

/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}

/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}

Swagger

启用Swagger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
* Created by XuanRan on 2022/10/10
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {


@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("dev.xuanran"))
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}

public ApiInfo apiInfo() {
return new ApiInfo("SpringSecurityTest", "SpringSecurityTest", "1.0",
"https://xuanran.cc", new Contact("XuanRan", "https://me.xuanran.cc", "XuanRanDev@qq.com")
, "GNU-2", "null", new ArrayList<>());
}


private List<ApiKey> securitySchemes() {
List<ApiKey> apiKeyList = new ArrayList<>();
apiKeyList.add(new ApiKey("Token", "Token", "header"));
return apiKeyList;
}

private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}

private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Token", authorizationScopes));
return securityReferences;
}

}

SpringSecurity

UserDetails

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import dev.xuanran.springsecurity.bean.LoginUser;
import dev.xuanran.springsecurity.bean.SysUser;
import dev.xuanran.springsecurity.exception.custom.UsernameOrPasswordErrorException;
import dev.xuanran.springsecurity.mapper.SysUserMapper;
import dev.xuanran.springsecurity.service.SysUserRoleService;
import dev.xuanran.springsecurity.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* Created by XuanRan on 2022/10/3
* SpringSecurity核心接口,负责根据用户名查询用户
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private SysUserMapper sysUserMapper;

@Autowired
private SysUserService sysUserService;

@Autowired
private SysUserRoleService sysUserRoleService;


@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser user = new LambdaQueryChainWrapper<>(sysUserMapper)
.eq(SysUser::getUsername, s)
.one();

if (Objects.isNull(user)) {
throw new UsernameOrPasswordErrorException();
}

List<String> permsList = sysUserMapper.getUserPermsKeyByUserId(user.getId());
List<String> roleList = sysUserMapper.getUserRoleKeyByUserId(user.getId())
.stream()
.map(s1 -> "ROLE_" + s1)
.collect(Collectors.toList());

permsList.addAll(roleList);
//封装成UserDetails对象返回
return new LoginUser(user, permsList);

}
}

AccessDenied

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.alibaba.fastjson.JSON;
import dev.xuanran.springsecurity.util.R;
import dev.xuanran.springsecurity.util.ResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Created by XuanRan on 2022/10/4
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseUtil.writeJson(response, R.error().message("权限不足"));
}
}

AuthEntryPoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import dev.xuanran.springsecurity.util.R;
import dev.xuanran.springsecurity.util.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Created by XuanRan on 2022/10/4
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.writeJson(response, R.error().message("认证失败,请重新登录"));
}
}

JWT Util

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
* JWT工具类
*/
public class JwtUtil {

//有效期为
public static final Long JWT_TTL = 60 * 60 * 10000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "XuanRan_Dev";

public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}

/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}

/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}

private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}

/**
* 创建token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}

public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}

/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getMimeDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}

/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}


}

JWT Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import dev.xuanran.springsecurity.bean.LoginUser;
import dev.xuanran.springsecurity.util.JwtUtil;
import dev.xuanran.springsecurity.util.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
* Created by XuanRan on 2022/10/4
* Token校验处理类
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

@Autowired
private RedisCache redisCache;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("Token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userid;
LoginUser loginUser = redisCache.getCacheObject(redisKey,LoginUser.class);

if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}

Other

全局异常捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import dev.xuanran.springsecurity.code.ErrorCodeEnum;
import dev.xuanran.springsecurity.exception.custom.UserNotLoginException;
import dev.xuanran.springsecurity.exception.custom.UsernameOrPasswordErrorException;
import dev.xuanran.springsecurity.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* Created by XuanRan on 2022/10/3
*/
@Slf4j
@RestControllerAdvice
public class ApplicationExceptionHandler {

@ExceptionHandler(Throwable.class)
public R exception(Throwable throwable) {
throwable.printStackTrace();
return R.error(ErrorCodeEnum.UNKNOW_EXCEPTION);
}

@ExceptionHandler(Exception.class)
public R exception(Exception e) {
e.printStackTrace();
return R.error().data(e.getMessage());
}

@ExceptionHandler(AccessDeniedException.class)
public R exception(AccessDeniedException accessDeniedException) {
accessDeniedException.printStackTrace();
return R.error(ErrorCodeEnum.ACCESS_DENIED);
}

}

自定义异常

1
2
3
4
5
6
7
8
9
/**
* Created by XuanRan on 2022/10/6
*/
public class UserNotLoginException extends RuntimeException{
public UserNotLoginException() {
super("用户未登录");
}
}

异常代码封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package dev.xuanran.springsecurity.code;

import lombok.Getter;

@Getter
public enum ErrorCodeEnum {

UNKNOW_EXCEPTION(30000, "系统未知异常"),
USERNAME_OR_PASSWORD_ERROR(30002, "用户名或密码错误"),
USERNAME_ALREADY_EXIST(30001, "用户名已存在"),
ACCESS_DENIED(50001,"没有访问权限"),
USER_NOT_LOGIN(30003, "用户未登录");

private Integer code;
private String msg;

ErrorCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}