springboot + security 自定义session过期处理方式

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);

你可能感兴趣的:(springboot,security)