Springboot + shiro
前情shiro 配置好后,登录成功后会生成 auth_token 保存并返回给前端 ,客户端往后每次发送请求都携带 auth_token 在请求头中,以此来判断登录状态。当请求不携带 auth_token 时判断为没登录,会跳转至提醒用户登录的接口(假设为 “/unauthorized”)。
问题发生过程当发送请求时,携带一个错误的 auth_token 请求头,此时不会跳转,后台会出现 ”shiro Enabling session validation scheduler…“ 的问题。
问题排查经过排查发现,下面是我自己写的 SessionManager ,用来判断请求是否携带了 “auth_token” 这个 token信息 ,如果携带了则把该 auth_token 返回交给 shiro 底层判断跟之前登录保存的 auth_token 是否一致,不一致则跳转到 “/unauthorized” 接口。
public class CustomWebSessionManager extends DefaultWebSessionManager {
public static final String AUTH_HEAD_TOKEN = "auth_token";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
if (request instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println(req.getRequestURL());
String sessionId = req.getHeader(AUTH_HEAD_TOKEN);
if (!StringUtils.isEmpty(sessionId)) {
return sessionId;
}
}
return super.getSessionId(request, response);
}
}
问题就出现在这里,我输入了错误的 auth_token, shiro 底层判断必然失败,此时内部需要重定向到 “/unauthorized” 接口,然后重定向时会生成了新的 JSESSIONID,变成了 “/unauthorized;JSESSIONID=xxxxxxxx” ,这样服务器会判断这是一个新的请求,又会被 CustomWebSessionManager 拦截并重新判断是否携带了 “auth_token” 信息,然后shiro 底层又判断没有登录,内部需要再次重定向到 “/unauthorized” 接口,又生成了一个新的 JSESSIONID。形成一个死循环,当循环一定次数后,结束循环,后台就会出现 ”shiro Enabling session validation scheduler…“ 的问题。
简单来说就是,服务器第一次请求 “/unauthorized” 接口,但是携带的是错误的 JSESSIONID,所以判断是没有登录,然后再次请求 “/unauthorized” 接口,此时 JSESSIONID 发生了改变,导致服务器误以为是新的请求,又进行一次判断。
所以初步的解决方案有2个
- 重定向时生成的 JSESSIONID 不要发生改变
- 重定向时不携带 JSESSIONID
注: 没有重写过 getSessionId() 方法才行
通过查阅网上资料,得知 DefaultWebSessionManager 有个 sessionIdUrlRewritingEnabled 的配置,设置成false可以防止重定向携带 JSessionID
在 ShiroConfig 中
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager defaultSessionManager = new CustomWebSessionManager();
//将sessionIdUrlRewritingEnabled属性设置成false,可以防止重定向携带 JSessionID
defaultSessionManager.setSessionIdUrlRewritingEnabled(false);
return defaultSessionManager;
}
@Bean
public DefaultWebSecurityManager securityManager(DefaultWebSessionManager webSessionManager) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//设置websession对象
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
修改过后问题没有得到解决。认真思考后发现问题所在,我在自定义的 WebSessionManager 中重写了 getSessionId() 方法 ,所以才导致 sessionIdUrlRewritingEnabled 的配置失败了。
查看父级的 getSessionId() 方法:
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = this.getSessionIdCookieValue(request, response);
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
} else {
id = this.getUriPathSegmentParamValue(request, "JSESSIONID");
if (id == null && request instanceof HttpServletRequest) {
String name = this.getSessionIdName();
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String queryString = httpServletRequest.getQueryString();
if (queryString != null && queryString.contains(name)) {
id = request.getParameter(name);
}
if (id == null && queryString != null && queryString.contains(name.toLowerCase())) {
id = request.getParameter(name.toLowerCase());
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url");
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, this.isSessionIdUrlRewritingEnabled());
return id;
}
通过阅读源码可以发现 getSessionId() 方法中 sessionIdUrlRewritingEnabled 的配置代码:
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, this.isSessionIdUrlRewritingEnabled());
方法二:
自定义 WebSessionManager
public class CustomWebSessionManager extends DefaultWebSessionManager {
public static final String AUTH_HEAD_TOKEN = "auth_token";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
if (request instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println(req.getRequestURL());
String sessionId = req.getHeader(AUTH_HEAD_TOKEN);
if (!StringUtils.isEmpty(sessionId)) {
// 禁止在url上拼接 JSessionId
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, this.isSessionIdUrlRewritingEnabled());
return sessionId;
}
}
return super.getSessionId(request, response);
}
}
总结
当没有自定义 WebSessionManager重写 getSessionId() 方法时,调用的是父级的方法,使用方法一也可以,但是如果自己重写了 getSessionId() 方法,加入自己的逻辑判断,则需要使用方法二设置。



