spring Security

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

相关配置

实现RedisSerializer接口

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {

public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

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

private final Class<T> clazz;

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

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

/**
* 反序列化
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}

配置redisTemplate

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
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
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
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);

// value序列化方式采用fastJson
template.setValueSerializer(fastJsonRedisSerializer);
// hash的value序列化方式采用fastJson
template.setHashValueSerializer(fastJsonRedisSerializer);

template.afterPropertiesSet();
return template;
}
}

redis通用工具

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

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

/**
* spring redis 工具类
**/
@Component
public class RedisUtil {

@Autowired
private RedisTemplate<Object, Object> redisTemplate;

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

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

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

/**
* 删除单个对象
*
* @param key
*/
public void deleteObject(Object key) {
redisTemplate.delete(key);
}

/**
* 删除集合对象
*
* @param collection
*/
public void deleteObject(Collection collection) {
redisTemplate.delete(collection);
}

public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}

public void expire(String key, int expire, TimeUnit timeUnit) {
redisTemplate.expire(key, expire, timeUnit);
}

/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public ListOperations<Object, Object> setCacheList(Object key, List<Object> dataList) {
ListOperations listOperation = redisTemplate.opsForList();
if (null != dataList) {
int size = dataList.size();
for (Object o : dataList) {
listOperation.leftPush(key, o);
}
}
return listOperation;
}

/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public List<Object> getCacheList(String key) {
List<Object> dataList = new ArrayList<>();
ListOperations<Object, Object> listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
if (null != size) {
for (int i = 0; i < size; i++) {
dataList.add(listOperation.index(key, i));
}
}
return dataList;
}

/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public BoundSetOperations<Object, Object> setCacheSet(String key, Set<Object> dataSet) {
BoundSetOperations<Object, Object> setOperation = redisTemplate.boundSetOps(key);
for (Object o : dataSet) {
setOperation.add(o);
}
return setOperation;
}

/**
* 获得缓存的set
*
* @param key
* @return
*/
public Set<Object> getCacheSet(Object key) {
Set<Object> dataSet = new HashSet<>();
BoundSetOperations<Object, Object> operation = redisTemplate.boundSetOps(key);
dataSet = operation.members();
return dataSet;
}

/**
* 缓存Map
*
* @param key
* @param dataMap
* @return
*/
public HashOperations<Object, Object, Object> setCacheMap(Object key, Map<Object, Object> dataMap) {
HashOperations hashOperations = redisTemplate.opsForHash();
if (null != dataMap) {
for (Map.Entry<Object, Object> entry : dataMap.entrySet()) {
hashOperations.put(key, entry.getKey(), entry.getValue());
}
}
return hashOperations;
}

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

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

jwt工具类

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
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* @author linZT
* @date 2022-4-1 14:39
* JWT工具类
*/
public class JwtUtils {

/**
* 两个常量: 过期时间;秘钥
*/
public static final long EXPIRE = 1000*60*60*24;
public static final String SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

/**
* 生成token字符串的方法
* @param id
* @param nickname
* @return
*/
public static String getJwtToken(String id,String nickname){
String JwtToken = Jwts.builder()
//JWT头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS2256")
//设置分类;设置过期时间 一个当前时间,一个加上设置的过期时间常量
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token主体信息,存储用户信息
.claim("id", id)
.claim("nickname", nickname)
//设置密钥
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
return JwtToken;
}

/**
* 判断token是否存在与有效
* @Param jwtToken
*/
public static boolean checkToken(String jwtToken){
if (StringUtils.isEmpty(jwtToken)){
return false;
}
try{
//验证token
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwtToken);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token是否存在与有效
* @Param request
*/
public static boolean checkToken(HttpServletRequest request){
try {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return false;
}
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}

/**
* 根据token获取会员id
* @Param request
*/
public static String getMemberIdByJwtToken(HttpServletRequest request){
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return "";
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return (String) body.get("id");
}
}

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
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
*/
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);
}
}

响应配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class WebUtils {

public static String renderString(HttpServletResponse response,String string){
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}catch (IOException e){
e.printStackTrace();
}
return null;
}
}

实现步骤

封装前台传过来的对象

springsecurity会将前端传来的数据(可能是一个实体类,或者只是用户名和密码)封装成Authentication对象,UsernamePasswordAuthenticationToken是其中的一个实现类,AuthenticationManager会对这个封装的对象进行认证

1
2
3
4
5
6
7
8
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
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
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;

@Override
public ResponseResult login(User user) {
//将登陆的信息封装成一个Authentication对象
//UsernamePasswordAuthenticationToken是其中的一个实现类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//authenticationManager对其进行校验
//会使用自定义的UserDetailsServiceImpl进行校验
Authentication authentication = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authentication)){
throw new RuntimeException("登录失败");
}
//认证通过,使用userId生成jwt,并且返回
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwtToken = JwtUtils.getJwtToken(userid);
Map<String, String> map = new HashMap<>();
map.put("token",jwtToken);
//将用户完整信息存入Redis
redisTemplate.opsForValue().set("login:"+userid,loginUser.getUser());
return new ResponseResult(200,"登录成功",map);
}
}

自定义数据校验方法

这一步是替换spring security从内存查数据的步骤,自定义为从数据库进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("user_name",username);
User user = userMapper.selectOne(userQueryWrapper);

if (user == null){
throw new RuntimeException("用户名错误");
}

//TODO 查询用户权限信息

return new LoginUser(user);
}
}

springsecurity 会将前台传来的密码进行加密,用加密后的数据与数据库进行比较,这也要求用户注册时存入密码也要使用相同的加密方式

1
2
3
4
5
6
7
8
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

自定义登录接口

1
2
3
4
5
6
7
8
9
10
@RestController
public class LoginContronller {
@Autowired
private LoginService loginService;

@PostMapping("/user/login")
public ResponseResult login(@RequestBody User user){
return loginService.login(user);
}
}
1
2
3
public interface LoginService {
ResponseResult login(User user);
}
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
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;

@Override
public ResponseResult login(User user) {
//将登陆的信息封装成一个Authentication对象
//UsernamePasswordAuthenticationToken是其中的一个实现类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//authenticationManager对其进行校验
//会使用前面自定义的UserDetailsServiceImpl进行校验
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//认证通过,使用userId生成jwt,并且返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwtToken = JwtUtils.getJwtToken(userid);
Map<String, String> map = new HashMap<>();
map.put("token",jwtToken);
//将用户完整信息存入Redis
redisTemplate.opsForValue().set("login:"+userid,loginUser);
return new ResponseResult(200,"登录成功",map);
}
}

需要对登录接口进行放行

1
2
3
4
5
6
7
8
9
10
11
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated();
}

认证过滤器

对于任意一个请求都需要进行拦截,对于携带Token的请求进行判断,如果成功就对其进行放行

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
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("myRedis")
private RedisTemplate redisTemplate;
@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;
}
String userid = null;
//解析token
try {
userid = JwtUtils.getMemberIdByJwtToken(token);
}catch (Exception e){
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:"+userid;
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(redisKey);
//存入SecruityContextHolder
if (Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//TODO 权限封装
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,null);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
1
2
3
4
5
6
7
8
9
10
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}

自定义退出接口

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public ResponseResult logout() {
//获取SecurityContextHolder中用户的信息
UsernamePasswordAuthenticationToken authentication
= (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();
redisTemplate.delete("login:"+userId);


return new ResponseResult(200,"注销成功");
}

授权

授权

再配置类添加注解

1
@EnableGlobalMethodSecurity(prePostEnabled = true)

相关方法上添加配置

1
@PreAuthorize("hasAuthority('响应权限')")
1
2
3
4
5
6
7
8
9
10
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

......

//TODO 查询用户权限信息
List<String> list = new ArrayList<>(Arrays.asList("自定义的权限","admin"));
return new LoginUser(user,list);
}
}
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
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

......

private List<String> permission;

public LoginUser(User user, List<String> permission) {
this.user = user;
this.permission = permission;
}

@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities==null){
return authorities;
}
authorities = permission.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

......

//TODO 权限封装
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
}
}

RBAC权限模型

用户表
id user_name password
1 kuang 123456
2 zhang 123456
角色表
id role
1 admin
2 worker
权限表
id menu
1 insert
2 update
3 delete
4 select
用户角色表
id user_id role_id
1 1 1
2 1 2
3 2 2
角色权限表
id role_id menu_id
1 1 1
2 1 2
3 1 3
4 1 4
5 2 4

根据用户id查询权限

1
2
3
4
5
6
7
SELECT DISTINCT menu.menu
FROM user user
LEFT JOIN user_role ur ON ur.user_id = user.id
LEFT JOIN role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN role role ON ur.role_id = role.id
LEFT JOIN menu menu ON rm.menu_id = menu.id
where user.id = '1'
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kuang.mapper.UserMapper">
<select id="selectMenuByUserId" resultType="java.lang.String">
SELECT DISTINCT menu.menu
FROM user user
LEFT JOIN user_role ur ON ur.user_id = user.id
LEFT JOIN role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN role role ON ur.role_id = role.id
LEFT JOIN menu menu ON rm.menu_id = menu.id
where user.id = #{userid}
</select>
</mapper>

自定义异常处理

再springsecurity中

认证出现的异常都会被封装成AuthenticationException,然后调用AuthenticationEntryPoint对象进行异常处理

授权出现的异常都会被封装成AccessDeniedException,然后调用AccessDeniedHandler进行异常处理

只需要自定义AuthenticationEntryPoint和AccessDeniedHandler类并注入到spring容器中即可实现自定义异常处理

1
2
3
4
5
6
7
8
9
10
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

ResponseResult responseResult = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败");
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(httpServletResponse,jsonString);
}
}
1
2
3
4
5
6
7
8
9
10
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
ResponseResult responseResult = new ResponseResult(HttpStatus.FORBIDDEN.value(), "用户授权失败");
String jsonString = JSON.toJSONString(responseResult);
WebUtils.renderString(httpServletResponse,jsonString);

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;

@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);
}
}

跨域

springboot配置跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {

registry
//设置允许跨域的路径
.addMapping("/**")
//设置允许跨域的请求
.allowedOriginPatterns("*")
//是否允许Cookic
.allowCredentials(true)
//设置允许的请求方式
.allowedMethods("GET","POST","DELETE","PUT")
//设置允许的header属性
.allowedHeaders("*")
//跨域允许时间
.maxAge(3600);

}
}

springsecurity配置跨域

1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf();
}
}