Shiro关闭session,无状态接入Springboot
# Shiro关闭session配置
# 前言
本文基于token进行身份认证,由于接入cas会和shiro的session管理冲突,所以关闭shiro的session,进行无状态管理。
特此记录一下shiro如何进行无状态管理。
# 一、引入依赖
此处引入的为 shiro-spring
,版本为 1.7.1
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 二、实现DefaultWebSubjectFactory
实现 DefaultWebSubjectFactory
关闭session
package com.hcframe.base.module.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
/**
* @author lhc
* @version 1.0
* @className StatelessDefaultSubjectFactory
* @date 2021年04月19日 1:54 下午
* @description 描述
*/
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 三、实现AuthenticationToken
此处是为了将用户信息改为token传递,通过token方式进行验证
package com.hcframe.base.module.shiro;
import org.apache.shiro.authc.AuthenticationToken;
/**
* @author lhc
* @version 1.0
* @className AuthToken
* @date 2021年04月19日 2:56 下午
* @description 实现shiro AuthenticationToken
*/
public class AuthToken implements AuthenticationToken {
private String token;
public AuthToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return 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
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
# 四、实现shiro的过滤器
此处为权限过滤器,具体内容参见注释
package com.hcframe.base.module.shiro;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author lhc
* @version 1.0
* @className AuthFilter
* @date 2021年04月19日 2:56 下午
* @description 实现shiro 过滤器
*/
public class AuthFilter extends AuthenticatingFilter {
/**
* @author lhc
* @description 创建token
* @date 4:35 下午 2021/4/26
* @params [request, response]
* @return org.apache.shiro.authc.AuthenticationToken
**/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 增加跨域支持
String myOrigin = httpServletRequest.getHeader("origin");
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
httpResponse.setCharacterEncoding("UTF-8");
// 返回错误状态信息
Map<String, Object> result = new HashMap<>();
result.put("code", 3);
result.put("msg", "未登陆");
String json = JSON.toJSONString(result);
httpResponse.getWriter().print(json);
return null;
}
return new AuthToken(token);
}
/**
* @author lhc
* @description 步骤1.所有请求全部拒绝访问
* @date 4:37 下午 2021/4/26
* @params [request, response, mappedValue]
* @return boolean
**/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}
/**
* @author lhc
* @description 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
* @date 4:37 下午 2021/4/26
* @params [request, response]
* @return boolean
**/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 增加跨域支持
String myOrigin = httpServletRequest.getHeader("origin");
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
httpResponse.setCharacterEncoding("UTF-8");
// 返回错误状态信息
Map<String, Object> result = new HashMap<>();
result.put("code", 3);
result.put("msg", "未登陆");
String json = JSON.toJSONString(result);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
/**
* @author lhc
* @description 登陆失败时候调用
* @date 4:38 下午 2021/4/26
* @params [token, e, request, response]
* @return boolean
**/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
//处理登录失败的异常
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String myOrigin = httpServletRequest.getHeader("origin");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
httpResponse.setCharacterEncoding("UTF-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
Map<String, Object> result = new HashMap<>();
result.put("code", 3);
result.put("msg", "未登陆");
String json = JSON.toJSONString(result);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* @author lhc
* @description 获取请求的token
* @date 4:38 下午 2021/4/26
* @params [httpRequest]
* @return java.lang.String
**/
private String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("X-Access-Token");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}
}
return 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
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
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
# 五、编写自定义的Realm
编写自定义realm,此步骤是为了定义权限校验和用户信息验证。
package com.hcframe.base.module.shiro;
import com.hcframe.base.common.config.FrameConfig;
import com.hcframe.base.module.shiro.service.ShiroService;
import com.hcframe.base.module.shiro.service.SystemRealm;
import com.hcframe.redis.RedisUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
/**
* @author lhc
* @version 1.0
* @className CustomRealm
* @date 2021年04月19日 2:56 下午
* @description 自定义Realm
*/
public class CustomRealm extends AuthorizingRealm {
@Resource
private RedisUtil redisUtil;
@Autowired
AuthService authService;
@Resource
private SystemRealm systemRealm;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Object user = principalCollection.getPrimaryPrincipal();
Map<String, Object> map = (Map<String, Object>) user;
// 从数据库读取权限,注入到shiro中
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> set = authService.getUserAuth(String.valueOf(map.get("ID")));
for (String auth : set) {
simpleAuthorizationInfo.addStringPermission(auth);
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String accessToken = (String) token.getPrincipal();
//1. 根据accessToken,查询用户信息
FtToken tokenEntity = shiroService.findByToken(accessToken);
userId = tokenEntity.getUserId();
//2. token失效
if (tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
Object user = shiroService.findByUserId(userId);
//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
if (user == null) {
throw new UnknownAccountException("用户不存在!");
}
//5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
return new SimpleAuthenticationInfo(user, accessToken, this.getName());
}
@Override
public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof AuthToken;
}
}
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
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
# 六、编写Shiro配置类
编写shiro配置类,将bean交给Spring管理
package com.hcframe.base.module.shiro;
import com.hcframe.base.module.shiro.service.SystemRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 不加这个注解不生效,具体不详
*/
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
/**
* 将自己的验证方式加入容器
*/
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCachingEnabled(false);
return customRealm;
}
/**
* @return org.apache.shiro.web.mgt.DefaultWebSubjectFactory
* @author lhc
* @description // 自定义subject工厂
* @date 4:50 下午 2021/4/19
* @params []
**/
@Bean
public DefaultWebSubjectFactory subjectFactory() {
return new StatelessDefaultSubjectFactory();
}
/**
* @return org.apache.shiro.session.mgt.SessionManager
* @author lhc
* @description // 自定义session管理器
* @date 5:50 下午 2021/4/19
* @params []
**/
@Bean
public SessionManager sessionManager() {
DefaultSessionManager shiroSessionManager = new DefaultSessionManager();
// 关闭session校验轮询
shiroSessionManager.setSessionValidationSchedulerEnabled(false);
return shiroSessionManager;
}
/**
* 权限管理,配置主要是Realm的管理认证
*/
@Bean("securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 禁用session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
// 设置自定义subject工厂
securityManager.setSubjectFactory(subjectFactory());
// 设置自定义session管理器
securityManager.setSessionManager(sessionManager());
// 设置自定义realm
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* Filter工厂,设置对应的过滤条件和跳转条件
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, Filter> filters = new HashMap<>(1);
// 设置自定义过滤器
filters.put("auth", new AuthFilter());
shiroFilterFactoryBean.setFilters(filters);
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 用户登陆
map.put("/ftUser/login", "anon");
// Vue静态资源
map.put("/img/**", "anon");
map.put("/static/**", "anon");
map.put("/tinymce/**", "anon");
map.put("/favicon.ico", "anon");
map.put("/manifest.json", "anon");
map.put("/robots.txt", "anon");
map.put("/precache*", "anon");
map.put("/service-worker.js", "anon");
// swagger UI 静态资源
map.put("/swagger-ui.html","anon");
map.put("/doc.html","anon");
map.put("/swagger-resources/**","anon");
map.put("/webjars/**","anon");
map.put("/v2/api-docs","anon");
map.put("/v2/api-docs-ext","anon");
map.put("/swagger/**","anon");
// druid 资源路径
map.put("/druid/**","anon");
// cas 接口
map.put("/cas/valid","anon");
map.put("/cas/logout","anon");
// 其余路径均拦截
map.put("/**", "auth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 加入注解的使用,不加入这个注解不生效
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
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
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
# 七、添加权限注解
此处只展示权限注解,其余注解请查询官方文档
注意
添加权限的注解必须被自定义拦截器拦截
否则会出现不调用自定义 CustomRealm
中的doGetAuthorizationInf()
方法的情况
代码示例:
@GetMapping("/system/list")
@RequiresPermissions(value = { "systemManage","system:list" },logical = Logical.OR)
public ResultVO<Integer> resetPassword(String userId,@PathVariable Integer version) {
return manageService.resetPassword(userId,version);
}
1
2
3
4
5
2
3
4
5
- value :字符串数组,填写之前realm中权限注入的字符串即可
- logical:逻辑关系,数组中权限的逻辑关系,分为AND和OR两种,默认为AND
上次更新: 2023/03/24, 08:53:10