最近集成框架用到shiro碰到url有时候会带上jsessionid有时候又没有。以前也碰到但是没有深入研究。
网上查了半天各种方法用了都没用。比如web.xml里面加session-config,添加DisableUrlSessionFilter 等等神马都没用。于是自己研究源码。说了半天废话终于进入正题。先申明下我是菜鸟。说的不对的错的请无视。有些是网上复制的。
先说下为什么网上那些玩意没用。
我们一般用的是DefaultWebSessionManager。shiro默认的是ServletContainerSessionManager这个非常简单的实现代表所有会话管理职责(包括会话集群如果 servlet 容器支持)运行 servlet 容器。 它本质上是一个桥 Shiro 会话 API 的 servlet 容器,没有别的。使用这个默认的一个好处是,使用现有的 servlet 容器的应用程序会话配置(超时,任何特定容器集群机制等)将正常工作。这个默认的缺点是,你与 servlet 容器的特定会话行为。 举个例子,如果你想集群会话,但你使用 Jetty 在生产、测试和 Tomcat 容器配置(或代码)将不具有可移植性。所以我们用DefaultWebSessionManager Shiro 的原生会话管理。所以网上的那些基于servlet 的不起作用。如果你用ServletContainerSessionManager也不存在这个问题。(后续有时间我会说说shiro和redis的集成。用redis管理session和cache。我是看了spring session、spring data redis的源码改造的所以跟spring能很好的集成具体后面再说。)
下面分析源码。先说下shiro的每次访问都会构建subject。
假设我们首次访问系统的登陆界面。然后我们登陆。在我们点击登陆之后会访问DefaultSecurityManager构建Subject。创建Subject之前会做很多事(创建cookie和session等等)其中会访问一下DefaultWebSessionManager的getReferencedSessionId方法。方法里面判断cookie里面是否有sessionid。肯定是没有的啦所以不会向request里面存放ShiroHttpServletRequest的3个静态属性。然后在org.apache.shiro.web.filter.authc.FormAuthenticationFilter登陆成功跳转页面的时候。issueSuccessRedirect方法里面会判断request是否有ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE如果有直接跳转到成功页面。具体判断的代码在ShiroHttpServletRequest里面的isRequestedSessionIdFromURL方法上。如果没有会在跳转成功页面url后面加上JSESSIONID。所以url上就多出了JSESSIONID.然后跳转首页时又会访问DefaultSecurityManager构建Subject。同样会进DefaultWebSessionManager的getReferencedSessionId方法这个时候sessionid有值所以request里面存放了
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE。这就是为什么我们删掉JSESSIONID他又不加上的原因。因为你后面每次访问。构建subject的时候sessionid有值request的里面也有值他就不会在url上加JSESSIONID了啊。假设我们删除浏览器cookie这个时候没有了sessionid。我们访问当前地址。同样会创建Subject。由于sessionid没有了所以request里面就不会存值了。然后会进入org.apache.shiro.web.filter.authc.UserFilter判断subject是否有认证信息。没有会通过saveRequestAndRedirectToLogin跳转到登陆页面。saveRequestAndRedirectToLogin里面判断request是否有值没值又会在跳转页面的url上加上JSESSIONID。然后进入登陆界面的时候构建subject 创建session request里面存值。好了说完了。看不懂得自己脑补把。
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = getSessionIdCookieValue(request, response);
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
} else {
//not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
//try the URI path segment parameters first:
id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
if (id == null) {
//not a URI path segment parameter, try the query parameters:
String name = getSessionIdName();
id = request.getParameter(name);
if (id == null) {
//try lowercase:
id = request.getParameter(name.toLowerCase());
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
}
}
public boolean isRequestedSessionIdFromURL() {
if (isHttpSessions()) {
return super.isRequestedSessionIdFromURL();
} else {
String value = (String) getAttribute(REFERENCED_SESSION_ID_SOURCE);
return value != null && value.equals(URL_SESSION_ID_SOURCE);
}
}
我的办法在每次跳转之前就判断sessionid是否有值。因为每次跳转之前subject就已经创建了session。至于如果禁用cookies。同样还是会在url上加上jsessionid。我们只是把判断给至前了。并没有修改原有逻辑。具体代码如下
/**
* clear JSESSIONID in URL if session id is not null
* {@link DefaultWebSessionManager} getReferencedSessionId
* {@link ShiroHttpServletRequest} isRequestedSessionIdFromURL
* @author Infinite Justice
*/
public class UserFilter extends AccessControlFilter{
private Cookie sessionIdCookie;
public Cookie getSessionIdCookie() {
return sessionIdCookie;
}
public void setSessionIdCookie(Cookie sessionIdCookie) {
this.sessionIdCookie = sessionIdCookie;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginRequest(request, response)) {
return true;
} else {
Subject subject = getSubject(request, response);
// If principal is not null, then the user is known and should be allowed access.
return subject.getPrincipal() != null;
}
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
saveRequest(request);
String sessionid = sessionIdCookie.readValue(WebUtils.toHttp(request), WebUtils.toHttp(response));
// clear JSESSIONID in URL if session id is not null
if(sessionid != null){request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionid);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
redirectToLogin(request, response);
return false;
}
}
/**
* clear JSESSIONID in URL if session id is not null
* {@link DefaultWebSessionManager} getReferencedSessionId
* {@link ShiroHttpServletRequest} isRequestedSessionIdFromURL
*
* @author Infinite Justice
*/
public class LoginFilter extends FormAuthenticationFilter{
private Cookie sessionIdCookie;
public Cookie getSessionIdCookie() {
return sessionIdCookie;
}
public void setSessionIdCookie(Cookie sessionIdCookie) {
this.sessionIdCookie = sessionIdCookie;
}
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
request.setAttribute(getFailureKeyAttribute(), ae);
}
@Override
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
String sessionid = sessionIdCookie.readValue(WebUtils.toHttp(request), WebUtils.toHttp(response));
// clear JSESSIONID in URL if session id is not null
if(sessionid != null){request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionid);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
super.issueSuccessRedirect(request, response);
}
}
另一种解决办法:
public class RedisWebSessionManager extends DefaultWebSessionManager {
/**
* Stores the Session's ID, usually as a Cookie, to associate with future requests.
* @param session the session that was just{@link #createSession created}.
*/
@Override
protected void onStart(Session session, SessionContext context) {
super.onStart(session, context);
ServletRequest request = WebUtils.getRequest(context);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
}
}
——————————————————————————————黄金分割线————————————————————————————————————————
如果你的shiro版本在1.3.2版本以上这个BUG已经解决只需要在配置文件如下配置中添加红色部分
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdUrlRewritingEnabled" value="false" />
<!-- 验证会话时会话的过期时间(毫秒) -->
<property name="globalSessionTimeout" value="3600000" />
<property name="sessionFactory" ref="sessionFactory" />
<property name="sessionValidationScheduler" ref="redisValidationScheduler" />
<property name="sessionDAO" ref="sessionDAO" />
<property name="sessionIdCookie" ref="sessionIdCookie" />
<property name="sessionListeners">
<list>
<ref bean="redisSessionListener" />
</list>
</property>
</bean>
源代码中的解释如下
// always set rewrite flag - SHIRO-361
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());