springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求
Posted L12345
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求相关的知识,希望对你有一定的参考价值。
一、解决跨域、过虑options请求问题
1.创建过虑类
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component @WebFilter(filterName = "SimpleCORSFilter",urlPatterns ="/*" ) public class SimpleCORSFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(SimpleCORSFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; logger.info("请求拦截:"+ ((HttpServletRequest) request).getRequestURI()); /* * 设置允许跨域请求 */ httpServletResponse.setHeader("Access-Control-Allow-Headers", "content-type, accept"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST"); httpServletResponse.setStatus(200); httpServletResponse.setContentType("text/plain;charset=utf-8"); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); httpServletResponse.setHeader("Access-Control-Max-Age", "0"); httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token,WG-Token, Authorization"); httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpServletResponse.setHeader("XDomainRequestAllowed", "1"); /* 过虑 OPTIONS 请求 */ String type = httpServletRequest.getMethod(); if (type.toUpperCase().equals("OPTIONS")) { return; } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { // String isCrossStr = filterConfig.getInitParameter("IsCross"); // isCross = isCrossStr.equals("true") ? true : false; // System.out.println(isCrossStr); } @Override public void destroy() { // isCross = false; } }
2.在 ShiroConfig.java 中设置拦截器
Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>(); filterMap.put("/sys/*",new SimpleCORSFilter()); // SimpleCORSFilter 为步骤1中创建的类 shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/index.html*", "anon"); //anon下不需要认证 filterChainDefinitionMap.put("/anon/**", "anon"); filterChainDefinitionMap.put("/loginSys", "anon"); filterChainDefinitionMap.put("/index", "anon"); filterChainDefinitionMap.put("/back", "anon"); filterChainDefinitionMap.put("/sys/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
二、前后端分离shiro管理session问题
SessionManager.java代码参考:https://blog.csdn.net/palerock/article/details/73457415?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
1.重写DefaultWebSessionManager的getSessionId方法
import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.Serializable; public class SessionManager extends DefaultWebSessionManager { private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class); private String authorization = "Authorization"; /** * 重写获取sessionId的方法调用当前Manager的获取方法 * * @param request * @param response * @return */ @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { return this.getReferencedSessionId(request, response); } /** * 获取sessionId从请求中 * * @param request * @param response * @return */ 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) { // 获取请求头中的session id = WebUtils.toHttp(request).getHeader(this.authorization); if (id == null) { String name = this.getSessionIdName(); id = request.getParameter(name); if (id == null) { 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); } return id; } // copy from super class private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) { if (!this.isSessionIdCookieEnabled()) { log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie."); return null; } else 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; return this.getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response)); } } // copy from super class private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) { if (!(servletRequest instanceof HttpServletRequest)) { return null; } else { HttpServletRequest request = (HttpServletRequest) servletRequest; String uri = request.getRequestURI(); if (uri == null) { return null; } else { int queryStartIndex = uri.indexOf(63); if (queryStartIndex >= 0) { uri = uri.substring(0, queryStartIndex); } int index = uri.indexOf(59); if (index < 0) { return null; } else { String TOKEN = paramName + "="; uri = uri.substring(index + 1); index = uri.lastIndexOf(TOKEN); if (index < 0) { return null; } else { uri = uri.substring(index + TOKEN.length()); index = uri.indexOf(59); if (index >= 0) { uri = uri.substring(0, index); } return uri; } } } } } // copy from super class private String getSessionIdName() { String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null; if (name == null) { name = "JSESSIONID"; } return name; } }
2.在 ShiroConfig.java 配置SessionManager
@Bean
public SecurityManager securityManager(MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean("sessionManager")
public SessionManager sessionManager(){
SessionManager manager = new SessionManager(); // SessionManager 为步骤1中创建的 SessionManager
/*使用了shiro自带缓存,
如果设置 redis为缓存需要重写CacheManager(其中需要重写Cache)
manager.setCacheManager(this.RedisCacheManager());*/
manager.setSessionDAO(new EnterpriseCacheSessionDAO());
return manager;
}
3.用户登录成功后,将sessionId返回前端
@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public String login(
@RequestParam(required = false) String username,
@RequestParam(required = false) String password
) {
JSONObject jsonObject = new JSONObject();
Subject subject = SecurityUtils.getSubject();
password = MD5Tools.MD5(password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
// 登录,即身份验证
subject.login(token);
onlineSessionManager.addOnlineSession(subject.getSession().getId());
User user = userService.getUserByLoginName(token.getUsername());
// 在session中存放用户信息
subject.getSession().setAttribute("userLogin", user);
jsonObject.put("error", 0);
jsonObject.put("msg", "登录成功");
// 返回sessionId
jsonObject.put("sessionId",subject.getSession().getId());
} catch (IncorrectCredentialsException e) {
throw new JsonException("用户名或密码错误", 405);
} catch (LockedAccountException e) {
throw new JsonException("登录失败,该用户已被冻结", 405);
} catch (AuthenticationException e) {
throw new JsonException("用户名或密码错误", 405);
}
return jsonObject.toString();
}
4.前端请求时请求头带上sessionId
模拟跨域请求的两种方法:
方法一:启动本地项目,浏览器随便打开某个网站,F12 > Console下执行以下代码实现跨域请求,最方便也是最容易实现
可参考:https://www.cnblogs.com/L237/p/12556488.html
方法二:启动本地项目,在自己的某台服务器上的某个项目下创建一个test.jsp页面,放入以下代码,然后通过浏览器访问test.jsp实现跨域请求
$.ajax({ type:\'post\', url:\'http://192.168.1.31:8080/login\', data:\'{"username":"admin","password":"admin"}\', contentType: "application/json", //提交数据类型 dataType:\'json\', success:function(data){ console.log(data); var sessionId = data.data.sessionId; //登录成功后返回的sessionId $.ajax({ type:\'post\', url:\'http://192.168.1.31:8080/sys/noticeNews/selectList\', data:\'{"pageNum":"1","pageSize":"10","verify":"2"}\', contentType: "application/json", //提交数据类型 headers: { "Authorization": sessionId, // Authorization 为重定的SessionManager类中定义的属性 "Content-Type":"application/json;charset=utf8" }, dataType:\'json\', crossDomain: true, success:function(data){ console.log(data); }, error:function (msg){ console.log(msg); } }); }, error:function (msg){ console.log(msg); layer.msg("用户名或密码错误"); } });
5. 在解决跨域类(一、1)SimpleCORSFilter中的httpServletResponse.setHeader() 中添加(二、4)Authorization
shiro配置类参考 ShiroConfig.java
package com.sx.ucenter.shiro; import com.sx.common.util.SimpleCORSFilter; import com.sx.ucenter.filter.SysFilter; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { //将自己的验证方式加入容器 @Bean public MyRealm myShiroRealm(EhCacheManager ehCacheManager) { MyRealm myRealm = new MyRealm(); return myRealm; } //权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager(MyRealm myRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setSessionManager(sessionManager()); return securityManager; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> map = new LinkedHashMap<>(); /* *常用的过滤器如下: *anon:所有用户可访问,通常作为指定页面的静态资源时使用 *authc:所有已登陆用户可访问 *roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致 *perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致 */ // //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据 // shiroFilterFactoryBean.setLoginUrl("/unauth"); /*跨域请求设置 start */ Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>(); // filterMap.put("authc",new SimpleCORSFilter()); filterMap.put("/sys/*",new SimpleCORSFilter()); shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/index.html*", "anon"); //anon下不需要认证 filterChainDefinitionMap.put("/anon/**", "anon"); filterChainDefinitionMap.put("/loginSys", "anon"); filterChainDefinitionMap.put("/index", "anon"); filterChainDefinitionMap.put("/back", "anon"); filterChainDefinitionMap.put("/sys/**", "authc"); // filterChainDefinitionMap.put("/**", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); /*跨域请求设置 end */ //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //开放匿名访问 //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); return shiroFilterFactoryBean; } /* * 设置session */ @Bean("sessionManager") public SessionManager sessionManager(){ SessionManager manager = new SessionManager(); /*使用了shiro自带缓存, 如果设置 redis为缓存需要重写CacheManager(其中需要重写Cache) manager.setCacheManager(this.RedisCacheManager());*/ manager.setSessionDAO(new EnterpriseCacheSessionDAO()); return manager; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
6.遇到的问题
参考:https://blog.csdn.net/wangjun5159/article/details/89875746
按照以上方式可以实现shiro 的管理了,但是发现登录成功后只要停2分钟没有任何操作 session就会失效需要重新登录,
后面发现是因为设置了ehcache缓存的原因,当时的ehcache设置如下:
timeToIdleSeconds配置为2分钟,如果2分钟内没有交互,session就会从ehcache中删除
<!-- 是否永久保存 --> <!-- 最大空闲时间 单位:秒 --> <!-- 存活时间 --> <!-- 溢出到磁盘 --> <!-- 磁盘上最大存储的对象数 --> <!-- 服务器重启后磁盘上的数据是否有效 --> <!-- 每隔多长时间去开启一次线程清理数据 --> <!-- 淘汰策略 最近一段时间利用率低的会被优先清理掉 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
以上是关于springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求的主要内容,如果未能解决你的问题,请参考以下文章
Vue +Springboot+shiro实现前后端分离权限控制
Springboot + Vue + shiro 实现前后端分离权限控制
SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo