您的位置:首页 > 其它

Shiro在请求头中获取sessionId以及rememberMe信息

2018-01-30 12:21 633 查看
本文介绍的内容需要对Shiro有一定了解,学习Shiro可查看跟开涛我学Shiro

解决问题步骤

重写
DefaultWebSessionManager
命名为
DefaultHeaderSessionManager


重写
CookieRememberMeManager
命名为
HeaderRememberMeManager


重写
ShiroFilterFactoryBean
,修改其中的默认Filters;

修改配置文件,指定为重写的类。

重写DefaultWebSessionManager

DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

DefaultWebSessionManager默认实现中,是通过Cookie确定sessionId,重写时,只需要把获取sessionid的方式变更为在request header中获取即可。

新建
DefaultHeaderSessionManager
类并 extends
DefaultSessionManager
以及 implements
WebSessionManager


//省略 import 信息
/**
* @author Created by yangyang on 2018/1/18.
* e-mail :yangyang_666@icloud.com ; tel :18580128658 ;QQ :296604153
*/
public class DefaultHeaderSessionManager extends DefaultSessionManager implements WebSessionManager {

}


request header中,我使用x-auth-token进行sessionid标识,接下来直接展示当前类的详细实现

//省略 import 信息
public class DefaultHeaderSessionManager extends DefaultSessionManager implements WebSessionManager {

// slf4j  logback
private static final Logger log = LoggerFactory.getLogger(DefaultHeaderSessionManager.class);

private final String X_AUTH_TOKEN = "x-auth-token";

// 请求头中获取 sessionId 并把sessionId 放入 response 中
private String getSessionIdHeaderValue(ServletRequest request, ServletResponse response) {
if (!(request instanceof HttpServletRequest)) {
log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
return null;
} else {
HttpServletRequest httpRequest = (HttpServletRequest) request;

// 在request 中 读取 x-auth-token 信息  作为 sessionId

String sessionId = httpRequest.getHeader(this.X_AUTH_TOKEN);

// 每次读取之后 都把当前的 sessionId 放入 response 中

HttpServletResponse httpResponse = (HttpServletResponse) response;

if (StringUtils.isNotEmpty(sessionId)) {
httpResponse.setHeader(this.X_AUTH_TOKEN, sessionId);
log.info("Current session ID is {}", sessionId);
}

return sessionId;
}
}

//获取sessionid
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = this.getSessionIdHeaderValue(request, response);

//DefaultWebSessionManager 中代码 直接copy过来
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
//不会把sessionid放在URL后
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.FALSE);
return id;
}

// 移除sessionid 并设置为 deleteMe 标识
private void removeSessionIdHeader(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.X_AUTH_TOKEN, "deleteMe");
}

/**
* 把sessionId 放入 response header 中
* onStart时调用
* 没有sessionid时 会产生sessionid 并放入 response header中
*/
private void storeSessionId(Serializable currentId, HttpServletRequest ignored, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
} else {
String idString = currentId.toString();

response.setHeader(this.X_AUTH_TOKEN, idString);

log.info("Set session ID header for session with id {}", idString);

log.trace("Set session ID header for session with id {}", idString);
}
}

// 创建session
protected Session createExposedSession(Session session, SessionContext context) {
if (!WebUtils.isWeb(context)) {
return super.createExposedSession(session, context);
} else {
ServletRequest request = WebUtils.getRequest(context);
ServletResponse response = WebUtils.getResponse(context);
SessionKey key = new WebSessionKey(session.getId(), request, response);
return new DelegatingSession(this, key);
}
}

protected Session createExposedSession(Session session, SessionKey key) {
if (!WebUtils.isWeb(key)) {
return super.createExposedSession(session, key);
} else {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
return new DelegatingSession(this, sessionKey);
}
}

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/response pair. No session ID cookie will be set.");
} else {
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
Serializable sessionId = session.getId();
this.storeSessionId(sessionId, request, response);
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
}

//获取sessionid
public Serializable getSessionId(SessionKey key) {
Serializable id = super.getSessionId(key);
if (id == null && WebUtils.isWeb(key)) {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
id = this.getSessionId(request, response);
}
return id;
}

protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
return this.getReferencedSessionId(request, response);
}

protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
super.onExpiration(s, ese, key);
this.onInvalidation(key);
}

protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
super.onInvalidation(session, ise, key);
this.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 header.");
this.removeSessionIdHeader(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
} else {
log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response pair. Session ID cookie will not be removed due to invalidated session.");
}

}

protected void onStop(Session session, SessionKey key) {
super.onStop(session, key);
if (WebUtils.isHttp(key)) {
HttpServletRequest request = WebUtils.getHttpRequest(key);
HttpServletResponse response = WebUtils.getHttpResponse(key);
log.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
this.removeSessionIdHeader(request, response);
} else {
log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response pair. Session ID cookie will not be removed due to stopped session.");
}
}

public boolean isServletContainerSessions() {
return false;
}


重写CookieRememberMeManger

默认情况下,Shiro会把rememberMe信息放入set-cookie中,保存在浏览器上。这里,重写Cookie方式,把rememberMe信息放入response header中。

创建
HeaderRememberMeManager
类并extends
AbstractRememberMeManager
,代码如下

//省略 import 信息
public class HeaderRememberMeManager extends AbstractRememberMeManager {

private static final transient Logger log = LoggerFactory.getLogger(HeaderRememberMeManager.class);

// header 中 固定使用的 key
public static final String DEFAULT_REMEMBER_ME_HEADER_NAME = "remember-me";

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet request and response in order to set the rememberMe cookie. Returning immediately and ignoring rememberMe operation.";
log.debug(msg);
}

} else {
HttpServletResponse response = WebUtils.getHttpResponse(subject);
String base64 = Base64.encodeToString(serialized);
// 设置 rememberMe 信息到 response header 中
response.setHeader(DEFAULT_REMEMBER_ME_HEADER_NAME, base64);
}
}

private boolean isIdentityRemoved(WebSubjectContext subjectContext) {
ServletRequest request = subjectContext.resolveServletRequest();
if (request == null) {
return false;
} else {
Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
return removed != null && removed;
}
}

protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a servlet request and response in order to retrieve the rememberMe cookie. Returning immediately and ignoring rememberMe operation.";
log.debug(msg);
}

return null;
} else {
WebSubjectContext wsc = (WebSubjectContext) subjectContext;
if (this.isIdentityRemoved(wsc)) {
return null;
} else {
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
// 在request header 中获取 rememberMe信息
String base64 = request.getHeader(DEFAULT_REMEMBER_ME_HEADER_NAME);
if ("deleteMe".equals(base64)) {
return null;
} else if (base64 != null) {
base64 = this.ensurePadding(base64);
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}

byte[] decoded = Base64.decode(base64);
if (log.isTraceEnabled()) {
log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
}

cbc8
return decoded;
} else {
return null;
}
}
}
}

private String ensurePadding(String base64) {
int length = base64.length();
if (length % 4 != 0) {
StringBuilder sb = new StringBuilder(base64);

for (int i = 0; i < length % 4; ++i) {
sb.append('=');
}

base64 = sb.toString();
}

return base64;
}

protected void forgetIdentity(Subject subject) {
if (WebUtils.isHttp(subject)) {
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
this.forgetIdentity(request, response);
}

}

public void forgetIdentity(SubjectContext subjectContext) {
if (WebUtils.isHttp(subjectContext)) {
HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);
HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);
this.forgetIdentity(request, response);
}
}

private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
//设置删除标示
response.setHeader(DEFAULT_REMEMBER_ME_HEADER_NAME, "deleteMe");
}

}


重写ShiroFilterFactoryBean

默认Fileter链中,user名称的过滤器在为登陆状态下会返回到登录界面,这里修改一下,为登陆状态直接放回Json字符串,不用跳转至登录页面。

如果使用了authc过滤 需要对重写 FormAuthenticationFilter ,为了适配App客户端,这里不推荐使用authc,可以在必须重新验证用户登陆信息时(使用rememberMe信息登陆无效)预先请求一下服务端或者通过记录的x-auth-token有效期进行判断。

新建
MyUserFilter
类 extends
UserFilter


public class MyUserFilter extends org.apache.shiro.web.filter.authc.UserFilter {

// isAccessAllowed return false 执行
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 这里也可以不用保存 保存当前request 可在登陆后重新请求当前 request
this.saveRequest(request);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().write("{\"code\":-1,\"message\":\"no.login\"}");
return false;
}

}


新建
MyDefaultFilter
enum

// 代码 为修改部分 只需要更改 user 执行为新建的 Filter
// 具体代码 可查看 org.apache.shiro.web.filter.mgt.DefaultFilter
public enum MyDefaultFilter {

user(MyUserFilter.class);

private final Class<? extends Filter> filterClass;

private MyDefaultFilter(Class<? extends Filter> filterClass) {
this.filterClass = filterClass;
}
}


新建
MyDefaultFilterChainManager
类 extends
DefaultFilterChainManager


public class MyDefaultFilterChainManager extends DefaultFilterChainManager {

protected void addDefaultFilters(boolean init) {

//使用我们创建的 DefaultFilter
MyDefaultFilter[] var2 = MyDefaultFilter.values();
int var3 = var2.length;

for (int var4 = 0; var4 < var3; ++var4) {
MyDefaultFilter defaultFilter = var2[var4];
super.addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}

}

}


新建
MyShiroFilterFactoryBean
类 extends
ShiroFilterFactoryBean


public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {

protected FilterChainManager createFilterChainManager() {
// 只要修改这里 使用我们创建的 DefaultFilterChainManager
MyDefaultFilterChainManager manager = new MyDefaultFilterChainManager();

//省略代码  请在 ShiroFilterFactoryBean 中 copy
}
// 缺省的private方法 需在 ShiroFilterFactoryBean 中 copy
}


修改配置信息,指向重写类

这里只展示和上述有关的配置信息,采用的注解@Bean 方式

@Bean(name = "rememberMeManager")
public HeaderRememberMeManager rememberMeManager() {
HeaderRememberMeManager headerRememberMeManager = new HeaderRememberMeManager();
// base64Encoded 自行生成一个 用于rememberMe加密
headerRememberMeManager.setCipherKey(base64Encoded);
return headerRememberMeManager;
}

@Bean
public DefaultHeaderSessionManager defaultWebSessionManager(SessionDAO sessionDAO) {
DefaultHeaderSessionManager defaultHeaderSessionManager = new DefaultHeaderSessionManager();
// 设立不使用 调取器验证 session 是否过期 作者使用了 redis ,这里根据SessionDAO实际情况设置
defaultHeaderSessionManager.setSessionValidationSchedulerEnabled(false);
defaultHeaderSessionManager.setSessionDAO(sessionDAO);
return defaultHeaderSessionManager;
}

@Bean(name = "shiroFilter")
public MyShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
// 使用自行创建的 FactoryBean
MyShiroFilterFactoryBean shiroFilterFactoryBean = new MyShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setFilterChainDefinitions("/api/v1/login = anon\n" +
"/ = anon\n" +
"/api/v1/website/article/** = anon\n" +
"/api/v1/** = cors,user\n");
return shiroFilterFactoryBean;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(                                                               DefaultHeaderSessionManager sessionManager,
RememberMeManager rememberMeManager) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//其他配置这里未列出
//DefaultHeaderSessionManager 重写的 sessionManager
defaultWebSecurityManager.setSessionManager(sessionManager);
// rememberMeManager 重写的 rememberMeManager
defaultWebSecurityManager.setRememberMeManager(rememberMeManager);
SecurityUtils.setSecurityManager(defaultWebSecurityManager);
return defaultWebSecurityManager;
}


完成以上配置信息就可以在 把sessionid或者rememberMe写入response header中和在 request header中读取,session超时自动销毁时间前端需和服务端保持一致,rememberMe有效时间由前端自行控制。

结束

本文介绍了一些项目中使用shiro的技巧,如果有错误或者有更好的方式,希望能与笔者联系,笔者QQ:296604153。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: