过期管理
1.直接在配置文件中配置:
server:
servlet:
session:
# 单位(s),默认30分钟,最少1分钟
timeout: 600
# session集群存储配置,如REDIS
# store-type: REDIS
多端登录管理和退出
退出处理:
使当前session失效
清除与当前用户相关的remember-me记录
清空当前的SecurityContext
重定向到登录页
2.更新WebSecurityConfig:
// session管理
@Autowired
private InvalidSessionStrategy invalidSessionStrategy;
@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
...
// session管理
.sessionManagement()
// 过期处理
.invalidSessionStrategy(invalidSessionStrategy)
// 同时在线数量
.maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
// 是否阻止登录
.maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
// 多端登录提示
.expiredSessionStrategy(sessionInformationExpiredStrategy)
.and()
.and()
// 退出
.logout()
// 触发退出操作的url
.logoutUrl("/signOut")
// .logoutSuccessUrl("demoLogout.html")
// 与logoutSuccessUrl只能调用其中一个
.logoutSuccessHandler(logoutSuccessHandler)
// 删除cookie
.deleteCookies("JSESSIONID")
.and()
// 对任何请求授权
.authorizeRequests()
// 匹配页面授权所有权限
.antMatchers(
// API
"/swagger-ui.html",
// 默认登录页
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
// 自定义登录页(demoLogin)
securityProperties.getBrowser().getLoginPage(),
// 验证码
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*",
securityProperties.getBrowser().getSignUpUrl(),
securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
securityProperties.getBrowser().getSignOutUrl(),
"/user/regist")
.permitAll()
实现LogoutSuccessHandler:
/**
* 默认的退出成功处理器,如果设置了demo.security.browser.signOutUrl,则跳到配置的地址上,
* 如果没配置,则返回json格式的响应。
*
* @author zhaohaibin
*/
@Slf4j
public class DemoLogoutSuccessHandler implements LogoutSuccessHandler {
public DemoLogoutSuccessHandler(String signOutSuccessUrl) {
this.signOutSuccessUrl = signOutSuccessUrl;
}
private String signOutSuccessUrl;
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.info("退出成功");
if (StringUtils.isBlank(signOutSuccessUrl)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse("退出成功")));
} else {
response.sendRedirect(signOutSuccessUrl);
}
}
}
实现InvalidSessionStrategy:
/**
* 默认的session失效处理策略
*
* @author zhaohaibin
*/
public class DemoInvalidSessionStrategy extends AbstractSessionStrategy implements InvalidSessionStrategy {
public DemoInvalidSessionStrategy(SecurityProperties securityProperties) {
super(securityProperties);
}
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
onSessionInvalid(request, response);
}
}
实现SessionInformationExpiredStrategy:
/**
* 并发登录导致session失效时,默认的处理策略
*
* @author zhaohaibin
*/
public class DemoExpiredSessionStrategy extends AbstractSessionStrategy implements SessionInformationExpiredStrategy {
public DemoExpiredSessionStrategy(SecurityProperties securityPropertie) {
super(securityPropertie);
}
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
onSessionInvalid(event.getRequest(), event.getResponse());
}
@Override
protected boolean isConcurrency() {
return true;
}
}
抽象继承:
/**
* 抽象的session失效处理器
*
* @author zhaohaibin
*/
@Slf4j
public class AbstractSessionStrategy {
/**
* 跳转的url
*/
private String destinationUrl;
/**
* 系统配置信息
*/
private SecurityProperties securityPropertie;
/**
* 重定向策略
*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 跳转前是否创建新的session
*/
private boolean createNewSession = true;
private ObjectMapper objectMapper = new ObjectMapper();
/**
* @param securityPropertie
*/
public AbstractSessionStrategy(SecurityProperties securityPropertie) {
String invalidSessionUrl = securityPropertie.getBrowser().getSession().getSessionInvalidUrl();
Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'");
Assert.isTrue(StringUtils.endsWithIgnoreCase(invalidSessionUrl, ".html"), "url must end with '.html'");
this.destinationUrl = invalidSessionUrl;
this.securityPropertie = securityPropertie;
}
protected void onSessionInvalid(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("session失效");
if (createNewSession) {
request.getSession();
}
String sourceUrl = request.getRequestURI();
String targetUrl;
// 如果是页面则跳转
if (StringUtils.endsWithIgnoreCase(sourceUrl, ".html")) {
// 跳到登录登出页面
if (StringUtils.equals(sourceUrl, securityPropertie.getBrowser().getSignInPage())
|| StringUtils.equals(sourceUrl, securityPropertie.getBrowser().getSignOutUrl())) {
targetUrl = sourceUrl;
// 跳到session过期页面
} else {
targetUrl = destinationUrl;
}
log.info("跳转到:" + targetUrl);
if (StringUtils.isNotBlank(SecurityConstants.DEFAULT_PROJECT_NAME_URL)) {
targetUrl = targetUrl.replace(SecurityConstants.DEFAULT_PROJECT_NAME_URL, "");
}
redirectStrategy.sendRedirect(request, response, targetUrl);
// 否则返回JSON提示
} else {
Object result = buildResponseContent(request);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}
/**
* @param request
* @return
*/
protected Object buildResponseContent(HttpServletRequest request) {
String message = "session已失效";
if (isConcurrency()) {
message = message + ",有可能是并发登录导致的";
}
return new SimpleResponse(message);
}
/**
* session失效是否是并发导致的
*
* @return
*/
protected boolean isConcurrency() {
return false;
}
/**
* Determines whether a new session should be created before redirecting (to
* avoid possible looping issues where the same session ID is sent with the
* redirected request). Alternatively, ensure that the configured URL does
* not pass through the {@code SessionManagementFilter}.
*
* @param createNewSession defaults to {@code true}.
*/
public void setCreateNewSession(boolean createNewSession) {
this.createNewSession = createNewSession;
}
}
Bean定义:
/**
* 浏览器环境下扩展点配置,配置在这里的bean,业务系统都可以通过声明同类型或同名的bean来覆盖安全
* 模块默认的配置。
*
* @author zhaohaibin
*/
@Configuration
public class BrowserSecurityBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
* session失效时的处理策略配置
*
* @return
*/
@Bean
@ConditionalOnMissingBean(InvalidSessionStrategy.class)
public InvalidSessionStrategy invalidSessionStrategy() {
return new DemoInvalidSessionStrategy(securityProperties);
}
/**
* 并发登录导致前一个session失效时的处理策略配置
*
* @return
*/
@Bean
@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
public SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
return new DemoExpiredSessionStrategy(securityProperties);
}
/**
* 退出时的处理策略配置
*
* @return
*/
@Bean
@ConditionalOnMissingBean(LogoutSuccessHandler.class)
public LogoutSuccessHandler logoutSuccessHandler() {
return new DemoLogoutSuccessHandler(securityProperties.getBrowser().getSignOutUrl());
}
}
更新SecurityConstants:
/**
* session失效默认的跳转地址
*/
String DEFAULT_SESSION_INVALID_URL = DEFAULT_PROJECT_NAME_URL + "/demoSessionInvalid.html";
配置:
新建Session属性配置类SessionProperties:
/**
* session管理相关配置项
*
* @author zhaohaibin
*/
@Data
public class SessionProperties {
/**
* 同一个用户在系统中的最大session数,默认1
*/
private int maximumSessions = 1;
/**
* 达到最大session时是否阻止新的登录请求,默认为false,不阻止,新的登录会将老的登录失效掉
*/
private boolean maxSessionsPreventsLogin;
/**
* session失效时跳转的地址
*/
private String sessionInvalidUrl = SecurityConstants.DEFAULT_SESSION_INVALID_URL;
}
更新BrowserProperties增加session属性配置:
/**
* 默认注册页
*/
private String signUpUrl= DEFAULT_PROJECT_NAME_URL + "signUp.html";
/**
* 默认登录页
*/
private String loginPage = SecurityConstants.DEFAULT_SIGN_IN_PAGE_URL;
/**
* 登录页面,当引发登录行为的url以html结尾时,会跳到这里配置的url上
*/
private String signInPage = SecurityConstants.DEFAULT_SIGN_IN_PAGE_URL;
/**
* session管理配置项
*/
private SessionProperties session = new SessionProperties();
/**
* 退出成功时跳转的url,如果配置了,则跳到指定的url,如果没配置,则返回json数据。
*/
private String signOutUrl;
对应失效跳转页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Session失效</title>
</head>
<body>
<h2>安全模块默认的session失效提示页面</h2>
<h3>请通过demo.security.browser.session.sessionInvalidUrl配置自己的页面URL</h3>
</body>
</html>
登录成功跳转页增加退出链接:
<!--默认退出url /logout-->
<a href="signOut">退出登录</a>
新建退出成功页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>退出成功</title>
</head>
<body>
<h2>退出成功</h2>
</body>
</html>
问题排查:
暂无