shiro session的获取最核心的类是 DefaultWebSessionManager
默认的sesionId是分两个步骤的,第一步是从Cookie中获取,Cookie没有的的话是从Url Path中获取
既然Shrio是从Cookie和Url中获取的SessionID,那么不适合Native手机的开发,一般会和Native开发人员定义一个Token协议,可以把Token放在Http header中,放Token有两种思路,第一种直接放入sessionId,第二种自定义Token生成然后Token和sessionId绑定.我更倾向于第二种方案. 继承类DefaultSessionManager实现自己的session获取方法.代码如下
package com.test.plats.uc.passport.client.support.session.mgt;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.WebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
/**
* @author mengxr
* @Description: Native SessionManager
* @date 2018/5/31 下午4:00
*/
public class LJNativeSessionManager extends DefaultSessionManager implements WebSessionManager {
private static final Logger log = LoggerFactory.getLogger(LJNativeSessionManager.class);
private static final String DEFAULT_TOKEN_KEY = "token_";
public static final String accessToken = "Access-Token";
private RedisTemplate sessionRedis;
private int sessionTimeoutInSec = 4 * 8600;
public RedisTemplate getSessionRedis() {
return sessionRedis;
}
public LJNativeSessionManager(RedisTemplate sessionRedis) {
this.sessionRedis = sessionRedis;
}
public void setSessionRedis(RedisTemplate sessionRedis) {
this.sessionRedis = sessionRedis;
}
/**
* Template method that allows subclasses to react to a new session being created.
*
* This method is invoked before any session listeners are notified.
*
* @param session the session that was just {@link #createSession created}.
* @param context the {@link SessionContext SessionContext} that was used to start the session.
*/
@Override
protected void onStart(Session session, SessionContext context) {
super.onStart(session, context);
if (!WebUtils.isHttp(context)) {
log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request " +
"pair. No session ID Access-Token will be set.");
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(context);
String accessToken = getAccessToken(request);
if (null != session && null != accessToken) {
String key = DEFAULT_TOKEN_KEY + accessToken;
String id = session.getId().toString();
byte[] bytes = sessionRedis.opsForValue().get(key);
if (id != null && null == bytes || bytes.length == 0) {
byte[] content = SerializeUtils.serialize(id);
sessionRedis.opsForValue().set(key, content, sessionTimeoutInSec, TimeUnit.SECONDS);
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
//automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
}
}
/**
* get access token
*
* @param request
* @return
*/
private String getAccessToken(HttpServletRequest request) {
String accessToken = request.getHeader(accessToken);
return accessToken != null ? accessToken : "";
}
@Override
protected Serializable getSessionId(SessionKey sessionKey) {
Serializable id = super.getSessionId(sessionKey);
if (null == id && WebUtils.isWeb(sessionKey)) {
HttpServletRequest request = WebUtils.getHttpRequest(sessionKey);
String accessToken = getAccessToken(request);
if (null != accessToken && accessToken.length() > 0) {
byte[] bytes = sessionRedis.opsForValue().get(DEFAULT_TOKEN_KEY + accessToken);
id = (Serializable) SerializeUtils.deserialize(bytes);
}
}
return id;
}
@Override
protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
super.onExpiration(s, ese, key);
onInvalidation(key);
}
@Override
protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
super.onInvalidation(s, ise, key);
onInvalidation(key);
}
private void onInvalidation(SessionKey key) {
ServletRequest request = WebUtils.getRequest(key);
if (request != null) {
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
}
if (WebUtils.isHttp(key)) {
log.debug("Referenced session was invalid. Removing session ID Access-Token.");
HttpServletRequest httpRequest = WebUtils.getHttpRequest(key);
String accessToken = getAccessToken(httpRequest);
if (null != accessToken && accessToken.length() > 0) {
sessionRedis.delete(DEFAULT_TOKEN_KEY + accessToken);
}
} else {
log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
"pair. Session ID Access-Token will not be removed due to invalidated session.");
}
}
@Override
protected void onStop(Session session, SessionKey key) {
super.onStop(session, key);
if (WebUtils.isHttp(key)) {
HttpServletRequest request = WebUtils.getHttpRequest(key);
log.debug("Session has been stopped (subject logout or explicit stop). Removing session ID Access-Token.");
String accessToken = getAccessToken(request);
if (null != accessToken && accessToken.length() > 0) {
sessionRedis.delete(DEFAULT_TOKEN_KEY + accessToken);
}
} else {
log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
"pair. Session ID Access-Token will not be removed due to stopped session.");
}
}
/**
* Returns {@code true} if session management and storage is managed by the underlying Servlet container or
* {@code false} if managed by Shiro directly (called 'native' sessions).
*
* If sessions are enabled, Shiro can make use of Sessions to retain security information from
* request to request. This method indicates whether Shiro would use the Servlet container sessions to fulfill its
* needs, or if it would use its own native session management instead (which can support enterprise features
* - like distributed caching - in a container-independent manner).
*
* @return {@code true} if session management and storage is managed by the underlying Servlet container or
* {@code false} if managed by Shiro directly (called 'native' sessions).
*/
@Override
public boolean isServletContainerSessions() {
return false;
}
}