大家在写项目时可能会遇到前后端分离的情况,那么这个时候就会遇到跨域请求的问题。我们知道在web环境下http是一种无状态的通讯协议,要想记录和校验用户的登录状态必须通过session的机制来实现,浏览器是通过cookie中存储的sessionid来确定用户的session数据的,shiro默认也是采用这种机制。
首先简要说一下怎样突破shiro跨域访问的限制,由于浏览器在访问后台服务前,会先发priflight请求(method=options),这时仍然会遇到跨域问题,为了解决priflight的请求问题,我们需要定义一个filter,对priflight的请求直接返回200,表示服务器允许priflight请求;对应的filter如下:
@Order(-100)
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/*",filterName = "shiroLoginFilter")
public class ShiroLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 允许哪些Origin发起跨域请求
//String orgin = request.getHeader("Origin");
// response.setHeader( "Access-Control-Allow-Origin", config.getInitParameter( "AccessControlAllowOrigin" ) );
response.setHeader( "Access-Control-Allow-Origin", "*" );
// 允许请求的方法
response.setHeader( "Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT" );
//多少秒内,不需要再发送预检验请求,可以缓存该结果
response.setHeader( "Access-Control-Max-Age", "3600" );
// 表明它允许跨域请求包含xxx头
response.setHeader( "Access-Control-Allow-Headers", "*" );
//允许暴露给客户端的响应头
response.setHeader("Access-Control-Expose-Headers","token");
//prefight请求
if (request.getMethod().equals( "OPTIONS" )) {
response.setStatus( 200 );
return;
}
chain.doFilter( servletRequest, response );
}
@Override
public void destroy() {
}
}
在这里尤其要注意一下设置24行response.setHeader( “Access-Control-Allow-Headers”, “*” );和26行response.setHeader(“Access-Control-Expose-Headers”,“token”);(设置token可以被js获取方便后续每次请求携带)的必要性,到这边跨域问题算是解决了,紧接着讨论跨域请求之后shiro的登录验证问题。
遇到的问题:
对于同源请求,浏览器是允许js访问cookie并在请求时携带的,但是对于来自h5应用或者小程序的跨域请求,浏览器的同源策略不允许js访问跨域的cookie,这样每次请求shiro获取的cookie都为空,自然就无法通过shiro的登录校验。对此问题,首先想到的改变登录时的校验方式,即自定义token对来自h5应用或者小程序的请求进行区别校验,下面给出具体方案及示例代码。
token身份鉴权的流程(方法一)
1.服务端在用户正常登录之后,通过特定算法生成一个全局唯一的字符串token设置到响应头中返回给客户端。
2.客户端在接下来的请求都会在请求头中携带token,服务端拦截token并对用户做身份鉴权。
3.token带有自动失效的机制,当用户主动退出或者失效时间一到则服务端删除该会话信息。
首先自定义过滤器(继承自shiro的FormAuthenticationFilter)并覆写它的isAccessAllowed方法,此方法返回值若为true则说明shiro鉴权通过,否则执行重写的redirectToLogin方法跳转到登录页面或者是返回具体错误原因,以下是示列代码:
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Autowired
private RedisManager redisManager;
@Override
protected boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
boolean isLogin;
String device = httpRequest.getHeader("device");
// 如果是客户端是H5或者小程序
if (StringUtils.isNotBlank(device) && device.equals("H5")) {
String h5Token = httpRequest.getHeader("token");
Cookie[] cookies = httpRequest.getCookies();
if (null != cookies) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")) {
cookie.setValue(h5Token);
}
}
}
isLogin = isH5Login(h5Token);//绕过shiro,直接到redis中校验token
} else {
// 如果是APP或者PC端,通过shiro本身进行验证
Subject subject = getSubject(request, response);
isLogin = subject.isAuthenticated();
}
return isLogin;
}
public boolean isH5Login(String h5Token){
return new RedisCache(redisManager).get(h5Token) != null;
}
}
@Override
protected void redirectToLogin(ServletRequest request,
ServletResponse response) throws IOException {
String loginUrl = getLoginUrl();
if (logger.isDebugEnabled()) {
logger.debug("客户端登录的URL:{}", loginUrl);
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
//System.out.println(httpRequest.getRequestURL());
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("text/html; charset=utf-8");
httpRequest.setCharacterEncoding("UTF-8");
// 是否为H5登录请求
if (StringUtils.isNotBlank(httpRequest.getHeader("device"))
&& httpRequest.getHeader("device").equals("H5")) {
String token = httpRequest.getHeader("token");
if (logger.isDebugEnabled()) {
logger.debug("客户端设备:{},token:{}", httpRequest.getHeader("device"), token);
}
if (StringUtils.isBlank(token)) {
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.getWriter().append(R.error("token不存在!").toString());
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
} else {
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.getWriter().append(R.error("认证失败!").toString());
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
} else {
// PC跳转 如果是非Ajax请求 按默认的配置跳转到登录页面
if (!"XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {// 不是ajax请求
WebUtils.issueRedirect(request, response, loginUrl);
} else {
// 如果是Aajx请求,则返回会话失效的JSON信息
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.getWriter().append(R.error("请求失败!").toString());
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
}
}
下面是登录代码:
@Log("登录")
@PostMapping("/login")
@ResponseBody
void login(String username, String password, HttpServletRequest request, HttpServletResponse response) throws IOException {
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
PrintWriter out = null;
try {
subject.login(token);
out = response.getWriter();
if(request.getHeader("device").equals("H5")){
String h5Token = UUID.randomUUID().toString();
new RedisCache(redisManager).put(h5Token,"H5"+username);
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("token", h5Token);
out.print(R.ok());
out.flush();
}else{
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out.print(R.error("非法请求"));
out.flush();
}
} catch (AuthenticationException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out = response.getWriter();
out.print(R.error("用户或密码错误"));
out.flush();
} finally {
if (out != null) {
out.close();
}
}
最后在前端代码中对xhr请求加上区分请求源的请求头:
<script th:inline="javascript">
$.ajaxSetup({
headers: {
"device": "H5"
}
})
</script>
在登录方法中,我是将登录之后生成的token设置为key存到redis并设置到响应头中,在此可以设置token在redis中的有效时间(我这里没有设置),isH5Login方法就是从redis中读取客户端传过来的token是否存在来校验是否通过,如果是同源请求则走shiro本身的校验机制。到这里基本上shiro的登录校验是绕过去了,其实这里并不是真的绕过,因为shiro该做的事情还是会照做,只不过是我们再到redis中去匹配一次而已,但是却带来了一个新的问题,那就是服务端通过SecurityUtils.getSubject().getSession();取到的用户session对象与之前登录时产生的session对象并不是同一个,原因是shiro本身在执行校验时由于无法获取到cookie中的token,所以它把这个请求当成是一个新的请求,每次调用都会创建一个新的session,但这个新session里面并不存在我们需要的用户相关登录信息。虽然此方法不失为一个解决方案,但是总觉得整个过程下来,代码和功能的实现都让人觉得很别扭,因此本文想在不改变整体的shiro校验机制情况下从源码的角度去逐步剖析shiro是如何拦截未登录请求的,从根源上来寻求解决方案,同时又不会对已对接好的业务接口造成影响,在提出方案之前下面先介绍另一种简单有效的方案。
跨域请求携带cookie(方法二)
那么如何让h5应用也能在跨域的情况下传输cookie呢?
首先服务端在使用cors协议时需要设置响应消息头Access-Control-Allow-Credentials的值为true即允许在ajax访问时携带cookie,客户端方面也需通过js设置withCredentials为true才能真正实现跨域传输cookie.另外为了安全,在cors标准里不允许Access-Control-Allow-Origin设置为*,而是必须指定明确的、与请求网页一致的域名.cookie也依然遵循“同源策略”,只有用目标服务器域名设置的cookie才会上传,而且使用document.cookie也无法读取目标服务器域名下的cookie。
接下来我们来看看代码是怎么实现的:
@Order(-100)
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/*",filterName = "shiroLoginFilter")
public class ShiroLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
String origin = request.getHeader("Origin");
if(origin == null) {
origin = request.getHeader("Referer");
}
// 允许哪些Origin发起跨域请求
response.setHeader( "Access-Control-Allow-Origin", origin );
// 允许请求的方法
response.setHeader( "Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT" );
//多少秒内,不需要再发送预检验请求,可以缓存该结果
response.setHeader( "Access-Control-Max-Age", "3600" );
// 表明它允许跨域请求包含xxx头
response.setHeader( "Access-Control-Allow-Headers", "*" );
//是否允许浏览器携带用户身份信息(cookie)
response.setHeader( "Access-Control-Allow-Credentials", "true" );
//prefight请求
if (request.getMethod().equals( "OPTIONS" )) {
response.setStatus( 200 );
return;
}
chain.doFilter( servletRequest, response );
}
@Override
public void destroy() {
}
}
其实看到这里,大家会发现依然是篇头讲到的自定义过滤器解决跨域问题的方法,只不过对其中的响应头做了必要的调整。
客户端也不再需要在请求头中带上token了,只要登录之后不管调什么接口都会自动带上cookie到后端校验的,代码如下:
$.ajax({
url:'http://localhost:8080/win/api/test/cors',
type:'post',
beforeSend:(xhr)=> {
//xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
//xhr.setRequestHeader("token", "web_session_key-5ce2ae9c-8f79-4f83-9b47-1510da4b2fb0");
xhr.setRequestHeader("device","H5");
},
xhrFields:{
withCredentials:true,
useDefaultXhrHeader:false
},
corssDomain:true,
success:function(data){
console.log(data);
}
});
这样接口就可以正常返回数据了,控制台也不再报错(注意request header中的cookie)。
修改shiro默认的会话管理器(方法三)
查看源码(具体执行流程可参考文末提供的参考文档获悉)可以知道shiro是在其默认的会话管理器DefaultWebSessionManager中获取请求携带过来的cookie的,我们可以通过继承这个类来扩展其中相关的代码来实现我们的需求,下面先贴出的是shiro从cookie中获取sessionid的主要源代码:
@Override
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 = getSessionId(request, response);
}
return id;
}
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
return getReferencedSessionId(request, response);
}
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = getSessionIdCookieValue(request, response);
.......
return id;
}
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
.......
//getSessionIdCookie().readValue()操作的是cookie对象.
return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
}
那么我们只要在继承类重写这些方法,通过在请求头传输过来的device标识便可以区分出不同的调用端来源,即pc端后台依然采用shiro原有的认证方式,而app端或者h5应用则可以使用基于token的身份认证方式,达到两者共存的目的.下面来看看我们自定义CustomerWebSessionManager类,其继承了shiro的DefaultWebSessionManager类,由于DefaultWebSessionManager中的大部分方法为私有的方法,无法为其子类所继承,所以只好重写其中所有用到的protected方法,代码如下:
private static final Logger logger = LoggerFactory.getLogger(CustomerWebSessionManager.class);
private static final String AUTH_TOKEN = "token";
public CustomerWebSessionManager() {
super();
}
/**
* 重写父类获取sessionID的方法,若请求为H5则从请求头中取出token,若为PC端后台则从cookie中获取
*
* @param request
* @param response
* @return
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
if (!(request instanceof HttpServletRequest)) {
logger.debug("Current request is not an HttpServletRequest - cannot get session ID. Returning null.");
return null;
}
HttpServletRequest httpRequest = WebUtils.toHttp(request);
if (StringUtils.isNotBlank(httpRequest.getHeader("device"))
&& httpRequest.getHeader("device").equals("H5")) {
//从header中获取token
String token = httpRequest.getHeader(AUTH_TOKEN);
// 每次读取之后都把当前的token放入response中
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (StringUtils.isNotEmpty(token)) {
httpResponse.setHeader(AUTH_TOKEN, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
//sessionIdUrlRewritingEnabled的配置为false,不会在url的后面带上sessionID
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return token;
}
return getReferencedSessionId(request, response);
}
/**
* shiro默认从cookie中获取sessionId
*
* @param request
* @param response
* @return
*/
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);
}
}
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);
}
// always set rewrite flag - SHIRO-361
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return id;
}
//copy from DefaultWebSessionManager
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
if (!isSessionIdCookieEnabled()) {
logger.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
return null;
}
if (!(request instanceof HttpServletRequest)) {
logger.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null.");
return null;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
}
//since 1.2.2 copy from DefaultWebSessionManager
private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
if (!(servletRequest instanceof HttpServletRequest)) {
return null;
}
HttpServletRequest request = (HttpServletRequest) servletRequest;
String uri = request.getRequestURI();
if (uri == null) {
return null;
}
int queryStartIndex = uri.indexOf('?');
if (queryStartIndex >= 0) { //get rid of the query string
uri = uri.substring(0, queryStartIndex);
}
int index = uri.indexOf(';'); //now check for path segment parameters:
if (index < 0) {
//no path segment params - return:
return null;
}
//there are path segment params, let's get the last one that may exist:
final String TOKEN = paramName + "=";
uri = uri.substring(index + 1); //uri now contains only the path segment params
//we only care about the last JSESSIONID param:
index = uri.lastIndexOf(TOKEN);
if (index < 0) {
//no segment param:
return null;
}
uri = uri.substring(index + TOKEN.length());
index = uri.indexOf(';'); //strip off any remaining segment params:
if (index >= 0) {
uri = uri.substring(0, index);
}
return uri; //what remains is the value
}
//since 1.2.1 copy from DefaultWebSessionManager
private String getSessionIdName() {
String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null;
if (name == null) {
name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
}
return name;
}
当shiro取不到sessionid时,会调用DelegatingSubject类中的getSession(true)方法创建一个新的session。
public Session getSession(boolean create) {
if (log.isTraceEnabled()) {
log.trace("attempting to get session; create = " + create +
"; session is null = " + (this.session == null) +
"; session has id = " + (this.session != null && session.getId() != null));
}
if (this.session == null && create) {
//added in 1.2:
if (!isSessionCreationEnabled()) {
String msg = "Session creation has been disabled for the current subject. This exception indicates " +
"that there is either a programming error (using a session when it should never be " +
"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
"for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
"for more.";
throw new DisabledSessionException(msg);
}
log.trace("Starting session for host {}", getHost());
SessionContext sessionContext = createSessionContext();
Session session = this.securityManager.start(sessionContext);
this.session = decorate(session);
}
return this.session;
}
上面的第22行this.securityManager.start最终调用的是DefaultWebSessionManager中的onStart方法,所以我们要重写这个方法,将产生的sessionid作为token放到response header中。另外当session失效或销毁时的相关方法也需重新实现,具体代码如下:
//存储会话id到response header中
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
}
String idString = currentId.toString();
if (StringUtils.isNotBlank(request.getHeader("device"))
&& request.getHeader("device").equals("H5")) {
response.setHeader(AUTH_TOKEN, idString);
} else {
Cookie template = getSessionIdCookie();
Cookie cookie = new SimpleCookie(template);
cookie.setValue(idString);
cookie.saveTo(request, response);
}
logger.trace("Set session ID cookie for session with id {}", idString);
}
//设置deleteMe到response header中
private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
if (StringUtils.isNotBlank(request.getHeader("device"))
&& request.getHeader("device").equals("H5")) {
response.setHeader(AUTH_TOKEN, Cookie.DELETED_COOKIE_VALUE);
} else {
getSessionIdCookie().removeFrom(request, response);
}
}
/**
* 会话创建
* 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);
if (!WebUtils.isHttp(context)) {
logger.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
"pair. No session ID cookie will be set.");
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
if (isSessionIdCookieEnabled()) {
Serializable sessionId = session.getId();
storeSessionId(sessionId, request, response);
} else {
logger.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId());
}
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
//会话失效
@Override
protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
super.onExpiration(s, ese, key);
onInvalidation(key);
}
@Override
protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
super.onInvalidation(session, 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)) {
logger.debug("Referenced session was invalid. Removing session ID cookie.");
removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
} else {
logger.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.");
}
}
//会话销毁
@Override
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);
logger.debug("Session has been stopped (subject logout or explicit stop). Removing session ID cookie.");
removeSessionIdCookie(request, response);
} else {
logger.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.");
}
}
到这里,对DefaultWebSessionManager中方法的重写就已经完成了,在onStart()调用我们重写的storeSessionId()方法可以看出是将新创建的sessionid作为token的值设置到响应头中,在后续请求时带上,在重写的getSessionId()中获取,交给后续方法进行校验。
最后再在springboot中做如下配置:
@Bean
public SimpleCookie wapsession() {
SimpleCookie simpleCookie = new SimpleCookie("token");
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* 自定义CustomerWebSessionManager实现shiro session的管理
*/
@Bean
public CustomerWebSessionManager sessionManager() {
CustomerWebSessionManager sessionManager = new CustomerWebSessionManager();
//会话验证器调度时间
sessionManager.setSessionValidationInterval(1800000);
//定时检查失效的session
sessionManager.setSessionValidationSchedulerEnabled(true);
//是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionIdCookie(wapsession());
sessionManager.setSessionIdCookieEnabled(true);
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
return sessionManager;
}
将token设到cookie中并告诉sessionManager作为会话id,前端测试代码示例如下:
$.ajax({
type: "POST",
url: "http://localhost:2018/login",
data: $('#signupForm').serialize(),
success: function (res,textStatus, request){
console.log(request.getAllResponseHeaders());
if (res.code == 0) {
$.ajax({
type: "POST",
url: "http://localhost:2018/agency/customer/postSuccess",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("token",request.getResponseHeader('token'));
},
success: function (r) {
document.write(r);
}
});
} else {
layer.msg(res.msg);
}
},
});
}
此外,还应设置全局的ajax请求头:
<script type="text/javascript">
$.ajaxSetup({
headers: {
"device": "H5"
}
})
</script>
在第一次登陆之后,可通过request.getResponseHeader(‘token’)将获取到的token存储起来或将token设置为全局的请求头,方便后续访问时带上,下面是由上面编写的代码的测试结果(注意其中request header和response header中的token):
到此,对shiro框架进行跨域请求的全部方法全部介绍完了。
参考文档:
[1]: https://www.jianshu.com/p/511d3adf95e9
[2]: https://www.jianshu.com/p/1c40cba55e0a
[3]: https://blog.csdn.net/wkyseo/article/details/79023191