SpringBoot+Shiro基于Redis实现共享Session

SpringBoot+Shiro基于Redis实现共享Session

项目发布到微服务k8s里,发现容器数量为1的时候 能正常登录,而当容器数量调整到多个的时候就会发现登录不了。 经排查是多个容器的时候Session会话没保持,就需要在多个应用的时候共享session会话。

上一篇2020-04-01-Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化已经提到了集成redis实现共享session的坑!! 这里我就不用自己去 RedisManager、SessionDAO了,而是使用shiro-redis 框架。

下面通过实现一个小Demo,来说明如何使用并集成shiro-redis!

一、实现步骤

  • pom.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>demo</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.crazycake/shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>


</project>
  • 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
package com.example.demo.conf;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;


@Configuration
@PropertySource("classpath:conf/redis.properties")
public class RedisConfig {

@Value("${shiro.redis.host}")
private String host;

@Value("${shiro.redis.timeout}")
private int timeout;

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getTimeout() {
return timeout;
}

public void setTimeout(int timeout) {
this.timeout = timeout;
}
}
  • Shiro配置文件
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
package com.example.demo.conf;

import com.example.demo.auth.PermissionRealm;
import com.example.demo.common.entity.User;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {

@Bean
public RedisConfig redisConfig(){
return new RedisConfig();
}

@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager(); // crazycake 实现
redisManager.setHost(redisConfig().getHost());
redisManager.setTimeout(redisConfig().getTimeout());
return redisManager;
}

@Bean
public JavaUuidSessionIdGenerator sessionIdGenerator(){
return new JavaUuidSessionIdGenerator();
}

@Bean
public RedisSessionDAO sessionDAO(){
RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现
sessionDAO.setRedisManager(redisManager());
sessionDAO.setSessionIdGenerator(sessionIdGenerator()); // Session ID 生成器
return sessionDAO;
}

@Bean
public SimpleCookie cookie(){
SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID"); // cookie的name,对应的默认是 JSESSIONID
cookie.setHttpOnly(true);
cookie.setPath("/"); // path为 / 用于多个系统共享JSESSIONID
return cookie;
}

@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(redisConfig().getTimeout()); // 设置session超时
sessionManager.setDeleteInvalidSessions(true); // 删除无效session
sessionManager.setSessionIdCookie(cookie()); // 设置JSESSIONID
sessionManager.setSessionDAO(sessionDAO()); // 设置sessionDAO
return sessionManager;
}

/**
* 1. 配置SecurityManager
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm()); // 设置realm
securityManager.setSessionManager(sessionManager()); // 设置sessionManager
// securityManager.setCacheManager(redisCacheManager()); // 配置缓存的话,退出登录的时候crazycake会报错,要求放在session里面的实体类必须有个id标识
return securityManager;
}

/**
* 2. 配置缓存
* @return
*/
// @Bean
// public CacheManager cacheManager(){
// EhCacheManager ehCacheManager = new EhCacheManager();
// ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
// return ehCacheManager;
// }

@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheManager cacheManager = new RedisCacheManager(); // crazycake 实现
cacheManager.setRedisManager(redisManager());
return cacheManager;
}

/**
* 3. 配置Realm
* @return
*/
@Bean
public AuthorizingRealm realm(){
PermissionRealm realm = new PermissionRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 指定加密算法
matcher.setHashAlgorithmName("MD5");
// 指定加密次数
matcher.setHashIterations(10);
// 指定这个就不会报错
matcher.setStoredCredentialsHexEncoded(true);
realm.setCredentialsMatcher(matcher);
return realm;
}

/**
* 4. 配置LifecycleBeanPostProcessor,可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}

/**
* 5. 启用IOC容器中使用Shiro的注解,但是必须配置第四步才可以使用
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}

/**
* 6. 配置ShiroFilter
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 静态资源
map.put("/css/**", "anon");
map.put("/js/**", "anon");

// 公共路径
map.put("/login", "anon");
map.put("/register", "anon");
//map.put("/*", "anon");

// 登出,项目中没有/logout路径,因为shiro是过滤器,而SpringMVC是Servlet,Shiro会先执行
map.put("/logout", "logout");

// 授权
map.put("/user/**", "authc,roles[user]");
map.put("/admin/**", "authc,roles[admin]");

// everything else requires authentication:
map.put("/**", "authc");

ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 配置SecurityManager
factoryBean.setSecurityManager(securityManager());
// 配置权限路径
factoryBean.setFilterChainDefinitionMap(map);
// 配置登录url
factoryBean.setLoginUrl("/");
// 配置无权限路径
factoryBean.setUnauthorizedUrl("/unauthorized");
return factoryBean;
}

/**
* 配置RedisTemplate,充当数据库服务
* @return
*/
@Bean
public RedisTemplate<String,User> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String,User> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
return redisTemplate;
}

}
  • UserServer.java
1
2
3
4
5
6
7
8
9
10
11
12
package com.example.demo.service;
import com.example.demo.common.entity.User;
import java.util.List;
public interface UserService {

void addUser(User user);

User login(User user);

List<User> getUsers();

}
  • UserServiceImpl.java
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
package com.example.demo.service.impl;

import com.example.demo.common.PasswordUtils;
import com.example.demo.common.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

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

@Service
public class UserServiceImpl implements UserService {

@Autowired
private RedisTemplate<String, User> redisTemplate;

@Override
public void addUser(User user) {
user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密
redisTemplate.boundHashOps("users").put(user.getUsername(), user);
}

@Override
public User login(User user) {
user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密
User u = (User) redisTemplate.boundHashOps("users").get(user.getUsername());
if (u == null || !check(user, u)){
return null;
}
return u;
}

@Override
public List<User> getUsers() {
List<Object> list = redisTemplate.boundHashOps("users").values();
List<User> users = new ArrayList<>();
list.forEach(u->{
users.add((User) u);
});
return users;
}

private boolean check(User a, User b){
if (a.getUsername().equals(b.getUsername()) && a.getPassword().equals(b.getPassword())){
return true;
}
return false;
}
}
  • IndexController.java
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
package com.example.demo.controller;

import com.example.demo.common.entity.User;
import com.example.demo.common.response.BaseResponse;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;


@RestController
public class IndexController {

@Autowired
private UserService userService;

@RequestMapping("/")
public ModelAndView index(){
return new ModelAndView("index");
}

@RequestMapping("/login")
public BaseResponse<String> login(@RequestBody User user){
BaseResponse<String> response = new BaseResponse<>(0,"登陆成功");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(
user.getUsername(), user.getPassword());
subject.login(token);
response.setData("/home");
return response;
}

@RequestMapping("/register")
public BaseResponse register(@RequestBody User user){
userService.addUser(user);
return new BaseResponse(0,"注册成功");
}

@RequestMapping("/home")
public ModelAndView home(){
ModelAndView mv = new ModelAndView("home");
mv.addObject("users", userService.getUsers());
return mv;
}
}
  • applicatin.properties
1
2
3
4
server.port=8080 

spring.redis.host=127.0.0.1
spring.redis.port=6379
  • index.html
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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Index</title>
<link th:href="@{css/index.css}" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<div class="main">
<div class="left">
<div class="form-group">
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<input type="password" name="password" placeholder="请输入密码">
</div>
<div class="form-group">
<a href="javascript:;" id="login">登录</a>
</div>
<div class="form-group">
<a href="/home">点我!不登录进不去</a>
</div>
</div>
<div class="right">
<div class="form-group">
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<input type="password" name="password" placeholder="请输入密码">
</div>
<div class="form-group">
<input type="text" name="show" placeholder="自我介绍">
</div>
<div class="form-group">
<a href="javascript:;" id="register">注册</a>
</div>
</div>
</div>
</div>
<script th:src="@{js/jquery-3.3.1.min.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>

二、本地测试

通过nginx 启动两个不同端口的jar(8081、8082)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream myapp{
server 127.0.0.1:8081 weight=1;
server 127.0.0.1:8082 weight=1;
}

server{
listen 80;
server_name myapp;

location / {
proxy_pass http://myapp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

项目启动会,访问登录 就会发现cookie里存在SHAREJSESSIONID了 redis里也有对应的SessionId了。

三、生产环境&ShiroConfig配置

很多时候生产环境的redis都是集群化,这里的配置就有一点不同。

下面贴一下我公司项目的ShiroConfig配置,跟上面的demo不是一回事 大体思想还是一致的。

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
package cn.pconline.pcloud.admin.config;

import cn.pconline.pcloud.admin.service.RoleService;
import cn.pconline.pcloud.admin.service.UserService;
import cn.pconline.pcloud.base.entity.system.Resource;
import cn.pconline.pcloud.base.entity.system.Role;
import cn.pconline.pcloud.base.entity.system.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.*;

/**
* @Description Shiro配置 支持session集群
* @Author jie.zhao
* @Date 2020/3/31 13:54
*/
@Configuration
public class ShiroConfig {

private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

@Value("${app.domain:/}")
private String domain;

@Value("${spring.redis.cluster.nodes}")
private String redisClusterNodes;

@Autowired
private UserService userService;

@Autowired
private RoleService roleService;

private static final int redis_expire = 1000 * 60 * 60 * 2;

private static final long session_expire = 1000 * 60 * 60 * 2;

/**
* 授权凭证(启动项目时加载)
* 对应 realm.doGetAuthorizationInfo()
*
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl(domain + "admin/login");
shiroFilter.setSuccessUrl(domain + "admin/index");
// 没权限时跳转至该页面
shiroFilter.setUnauthorizedUrl(domain + "admin/permission/died");
// anon、authc、user对应realm.doGetAuthenticationInfo(..)登录认证
// perms、roles、ssl、est、port对应realm.doGetAuthorizationInfo(..)授权认证
// 设置过滤器链接集合 注意:Map要支持顺序,授权配置后出
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}

@Bean
public RedisClusterManager redisClusterManager() {
RedisClusterManager redisManager = new RedisClusterManager();
redisManager.setHost(redisClusterNodes);
return redisManager;
}

@Bean
public RedisSessionDAO redisSessionDAO(RedisClusterManager redisClusterManager) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisClusterManager);
// Session ID 生成器
redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
return redisSessionDAO;
}

@Bean
public SimpleCookie cookie() {
// cookie的name,对应的默认是 JSESSIONID
SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
cookie.setHttpOnly(true);
// path为 / 用于多个系统共享JSESSIONID
cookie.setPath("/");
return cookie;
}

@Bean
public RedisCacheManager redisCacheManager(RedisClusterManager redisClusterManager) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisClusterManager);
redisCacheManager.setExpire(redis_expire);
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}

@Bean
public SecurityManager securityManager(AuthorizingRealm myShiroRealm, RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session超时
sessionManager.setGlobalSessionTimeout(session_expire);
// 删除无效session
sessionManager.setDeleteInvalidSessions(true);
// 设置JSESSIONID
sessionManager.setSessionIdCookie(cookie());
// 设置sessionDAO
sessionManager.setSessionDAO(redisSessionDAO);

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(redisCacheManager);
securityManager.setRememberMeManager(cookieRememberMeManager());
securityManager.setRealm(myShiroRealm);
return securityManager;
}

@Bean
public AuthorizingRealm myShiroRealm() {
AuthorizingRealm myShiroRealm = new AuthorizingRealm() {

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("认证 --> MyShiroRealm.doGetAuthenticationInfo()");
String username = (String) token.getPrincipal();
User user = userService.findByAccount(username);
String password = new String((char[]) token.getCredentials());
// 账号不存在
if (user == null) {
throw new UnknownAccountException("账号不存在!");
}
// 密码错误
/*if (!MD5Utils.md5(password).equals(user.getPassword())) {
throw new IncorrectCredentialsException("账号或密码不正确");
}*/
// 账号锁定
if (user.getIsLock() == 1) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("权限配置 --> MyShiroRealm.doGetAuthorizationInfo()");
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> shiroPermissions = new HashSet<>();
Set<String> roleSet = new HashSet<String>();
// 加载你的角色
List<Role> roles = roleService.list4Login(user);
if (roles != null) {
for (Role role : roles) {
// 添加角色
roleSet.add(role.getRoleKey());
// 添加角色关联的资源
if (role.getRelResourceList() != null) {
for (Resource resource : role.getRelResourceList()) {
shiroPermissions.add(resource.getSourceKey());
}
}
}
}
authorizationInfo.setRoles(roleSet);
authorizationInfo.setStringPermissions(shiroPermissions);
return authorizationInfo;
}
};
myShiroRealm.setCachingEnabled(true);
myShiroRealm.setAuthorizationCachingEnabled(true);
return myShiroRealm;
}

/**
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}

/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}

@Bean
public SimpleMappingExceptionResolver resolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}

@Bean
public SimpleCookie rememberMeCookie() {
logger.info("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

//<!-- 记住我cookie生效时间 ,单位秒;-->
simpleCookie.setMaxAge(1800);
return simpleCookie;
}

@Bean
public CookieRememberMeManager cookieRememberMeManager() {
logger.info("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCookie(rememberMeCookie());
return manager;
}
}

参考文档:

-------------已经触及底线 感谢您的阅读-------------

本文标题:SpringBoot+Shiro基于Redis实现共享Session

文章作者:趙小傑~~

发布时间:2020年04月01日 - 16:49:43

最后更新:2020年04月01日 - 17:48:04

原始链接:https://cnsyear.com/posts/2f7e3734.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%