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

xiaoxiao2021-02-28  70

本文介绍的内容需要对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."); } 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。

转载请注明原文地址: https://www.6miu.com/read-1750391.html

最新回复(0)