security校验session校验是在ConcurrentSessionFilter,在doFilter方法里可以看到如果session过期会执行方法
this.doLogout(request, response);
this.sessionInformationExpiredStrategy.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
先做退出操作,在执行session过期策略,这个策略的初始化是在SessionManagementConfigurer.getExpiredSessionStrategy
SessionInformationExpiredStrategy getExpiredSessionStrategy() {
if(this.expiredSessionStrategy != null) {
return this.expiredSessionStrategy;
} else if(this.expiredUrl == null) {
return null;
} else {
if(this.expiredSessionStrategy == null) {
this.expiredSessionStrategy = new SimpleRedirectSessionInformationExpiredStrategy(this.expiredUrl);
}
return this.expiredSessionStrategy;
}
}
所以如果你没有设置默认的策略和expiredUrl
private ConcurrentSessionFilter createConccurencyFilter(H http) {
SessionInformationExpiredStrategy expireStrategy = this.getExpiredSessionStrategy();
SessionRegistry sessionRegistry = this.getSessionRegistry(http);
return expireStrategy == null?new ConcurrentSessionFilter(sessionRegistry):new ConcurrentSessionFilter(sessionRegistry, expireStrategy);
}
构参里默认的是ResponseBodySessionInformationExpiredStrategy策略,
public ConcurrentSessionFilter(SessionRegistry sessionRegistry) {
Assert.notNull(sessionRegistry, "SessionRegistry required");
this.sessionRegistry = sessionRegistry;
this.sessionInformationExpiredStrategy = new ConcurrentSessionFilter.ResponseBodySessionInformationExpiredStrategy(null);
}
默认策略里如果session过期,退出后执行的是,就是往response里写了一段话,这不是我们想要的结果,所以这里只有自定义策略,复写onExpiredSessionDetected就可以了
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletResponse response = event.getResponse();
response.getWriter().print("This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).");
response.flushBuffer();
}
所以这里自定义
@Component
public class AjaxSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletResponse response = event.getResponse();
HttpServletRequest request = event.getRequest();
JSONObject returnObj = new JSONObject();
if (RequestUtils.isAjax(request)) {
returnObj.put("status", "0");
} else {
returnObj.put("status", "-1");
returnObj.put("message", "非法登录");
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(returnObj.toJSONString());
response.flushBuffer();
}
}
这里设置contentType需要注意
如果是
response.getWriter().print(returnObj.toJSONString());
response.setContentType("application/json;charset=UTF-8");
那设置的编码格式就会失败,看源码就会知道
public void setContentType(String type) {
if(type != null && !this.insideInclude && !this.responseStarted()) {
ContentTypeInfo ct = this.servletContext.parseContentType(type);
this.contentType = ct.getContentType();
boolean useCharset = false;
if(ct.getCharset() != null && this.writer == null && !this.isCommitted()) {
this.charset = ct.getCharset();
this.charsetSet = true;
useCharset = true;
}
if(!useCharset && this.charsetSet) {
if(ct.getCharset() == null) {
this.exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader() + "; charset=" + this.charset);
} else {
this.exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getContentType() + "; charset=" + this.charset);
}
} else {
this.exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader());
}
}
}
如果先往writer里写入内容,那么this.writer == null就是false,useCharset 就是false,设置的编码就是ISO-8859-1,在客户端接受的中文会乱码。
之后再securityConfig里配置
http.sessionManagement().
/**
* 同一个账号只能在一个地方登陆
*/
maximumSessions(1).
/**
* 自定义session过期策略,替代默认的{@link ConcurrentSessionFilter.ResponseBodySessionInformationExpiredStrategy},
* 复写onExpiredSessionDetected方法,默认方法只输出异常,没业务逻辑。这里需要返回json
*/
expiredSessionStrategy(ajaxSessionInformationExpiredStrategy);