spring 安全,如何使用户的所有会话过期
spring security, how to expire all sessions of a user
我必须解决以下情况,在 Spring 安全性 3.2.5-RELEASE
和 Spring Core 4.1.2-RELEASE
应用程序 运行 Java 1.7 on wildfly 8.1 中。
- 用户'bob'登录
- 并且管理员删除了 'bob'
- 如果 'bob' 注销,他将无法再次登录,但他的当前会话仍保持活动状态。
我想把'bob'踢出去
//this doesn't work
for (final SessionInformation session : sessionRegistry.getAllSessions(user, true)) {
session.expireNow();
}
- 添加应用程序事件侦听器以跟踪
HttpSessionCreatedEvent
和HttpSessionDestroyedEvent
并将其注册为ApplicationListener
并维护一个SessionId缓存到HttoSession。
- (可选)添加您自己的 ApplicationEvent class
AskToExpireSessionEvent
-
- 在您的用户管理服务中,将依赖项添加到
SessionRegistry
和 ApplicationEventPublisher
以便您可以列出当前活动的用户会话并找到那些(因为可能有很多)活动的您正在寻找的用户,即 'bob'
- 删除用户时,会为他的每个会话分派
AskToExpireSessionEvent
。
- 使用弱引用 HashMap 来跟踪会话
用户服务:
@Service
public class UserServiceImpl implements UserService {
/** {@link SessionRegistry} does not exists in unit tests */
@Autowired(required = false)
private Set<SessionRegistry> sessionRegistries;
@Autowired
private ApplicationEventPublisher publisher;
/**
* destroys all active sessions.
* @return <code>true</code> if any session was invalidated^
* @throws IllegalArgumentException
*/
@Override
public boolean invalidateUserByUserName(final String userName) {
if(null == StringUtils.trimToNull(userName)) {
throw new IllegalArgumentException("userName must not be null or empty");
}
boolean expieredAtLeastOneSession = false;
for (final SessionRegistry sessionRegistry : safe(sessionRegistries)) {
findPrincipal: for (final Object principal : sessionRegistry.getAllPrincipals()) {
if(principal instanceof IAuthenticatedUser) {
final IAuthenticatedUser user = (IAuthenticatedUser) principal;
if(userName.equals(user.getUsername())) {
for (final SessionInformation session : sessionRegistry.getAllSessions(user, true)) {
session.expireNow();
sessionRegistry.removeSessionInformation(session.getSessionId());
publisher.publishEvent(AskToExpireSessionEvent.of(session.getSessionId()));
expieredAtLeastOneSession = true;
}
break findPrincipal;
}
} else {
logger.warn("encountered a session for a none user object {} while invalidating '{}' " , principal, userName);
}
}
}
return expieredAtLeastOneSession;
}
}
申请事件:
import org.springframework.context.ApplicationEvent;
public class AskToExpireSessionEvent extends ApplicationEvent {
private static final long serialVersionUID = -1915691753338712193L;
public AskToExpireSessionEvent(final Object source) {
super(source);
}
@Override
public String getSource() {
return (String)super.getSource();
}
public static AskToExpireSessionEvent of(final String sessionId) {
return new AskToExpireSessionEvent(sessionId);
}
}
http 会话缓存侦听器:
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.web.session.HttpSessionCreatedEvent;
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
import org.springframework.stereotype.Component;
import com.cb4.base.service.event.AskToExpireSessionEvent;
@Component
public class HttpSessionCachingListener {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionCachingListener.class);
private final Map<String, HttpSession> sessionCache = new WeakHashMap<>();
void onHttpSessionCreatedEvent(final HttpSessionCreatedEvent event){
if (event != null && event.getSession() != null && event.getSession().getId() != null) {
sessionCache.put(event.getSession().getId(), event.getSession());
}
}
void onHttpSessionDestroyedEvent(final HttpSessionDestroyedEvent event){
if (event != null && event.getSession() != null && event.getSession().getId() != null){
sessionCache.remove(event.getSession().getId());
}
}
public void timeOutSession(final String sessionId){
if(sessionId != null){
final HttpSession httpSession = sessionCache.get(sessionId);
if(null != httpSession){
logger.debug("invalidating session {} in 1 second", sessionId);
httpSession.setMaxInactiveInterval(1);
}
}
}
@Component
static class HttpSessionCreatedLisener implements ApplicationListener<HttpSessionCreatedEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final HttpSessionCreatedEvent event) {
parent.onHttpSessionCreatedEvent(event);
}
}
@Component
static class HttpSessionDestroyedLisener implements ApplicationListener<HttpSessionDestroyedEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final HttpSessionDestroyedEvent event) {
parent.onHttpSessionDestroyedEvent(event);
}
}
@Component
static class AskToTimeOutSessionLisener implements ApplicationListener<AskToExpireSessionEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final AskToExpireSessionEvent event) {
if(event != null){
parent.timeOutSession(event.getSource());
}
}
}
}
使用 java 配置在 class 中添加以下代码扩展 WebSecurityConfigurerAdapter :
@Bean
public SessionRegistry sessionRegistry( ) {
SessionRegistry sessionRegistry = new SessionRegistryImpl( );
return sessionRegistry;
}
@Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}
并在您的 configure( HttpSecurity http ) 方法中添加以下内容:
http.sessionManagement( ).maximumSessions( -1 ).sessionRegistry( sessionRegistry( ) );
http.sessionManagement( ).sessionFixation( ).migrateSession( )
.sessionAuthenticationStrategy( registerSessionAuthStr( ) );
此外,在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy,如下所示:
usernamePasswordAuthenticationFilter
.setSessionAuthenticationStrategy( registerSessionAuthStr( ) );
注意:在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy 会导致填充主体列表,因此当您尝试从 sessionRegistry 获取所有主体的列表时(sessionRegistry.getAllPrinicpals() ),列表不为空。
我必须解决以下情况,在 Spring 安全性 3.2.5-RELEASE
和 Spring Core 4.1.2-RELEASE
应用程序 运行 Java 1.7 on wildfly 8.1 中。
- 用户'bob'登录
- 并且管理员删除了 'bob'
- 如果 'bob' 注销,他将无法再次登录,但他的当前会话仍保持活动状态。
我想把'bob'踢出去
//this doesn't work for (final SessionInformation session : sessionRegistry.getAllSessions(user, true)) { session.expireNow(); }
- 添加应用程序事件侦听器以跟踪
HttpSessionCreatedEvent
和HttpSessionDestroyedEvent
并将其注册为ApplicationListener
并维护一个SessionId缓存到HttoSession。 - (可选)添加您自己的 ApplicationEvent class
AskToExpireSessionEvent
- - 在您的用户管理服务中,将依赖项添加到
SessionRegistry
和ApplicationEventPublisher
以便您可以列出当前活动的用户会话并找到那些(因为可能有很多)活动的您正在寻找的用户,即 'bob' - 删除用户时,会为他的每个会话分派
AskToExpireSessionEvent
。 - 使用弱引用 HashMap 来跟踪会话
用户服务:
@Service
public class UserServiceImpl implements UserService {
/** {@link SessionRegistry} does not exists in unit tests */
@Autowired(required = false)
private Set<SessionRegistry> sessionRegistries;
@Autowired
private ApplicationEventPublisher publisher;
/**
* destroys all active sessions.
* @return <code>true</code> if any session was invalidated^
* @throws IllegalArgumentException
*/
@Override
public boolean invalidateUserByUserName(final String userName) {
if(null == StringUtils.trimToNull(userName)) {
throw new IllegalArgumentException("userName must not be null or empty");
}
boolean expieredAtLeastOneSession = false;
for (final SessionRegistry sessionRegistry : safe(sessionRegistries)) {
findPrincipal: for (final Object principal : sessionRegistry.getAllPrincipals()) {
if(principal instanceof IAuthenticatedUser) {
final IAuthenticatedUser user = (IAuthenticatedUser) principal;
if(userName.equals(user.getUsername())) {
for (final SessionInformation session : sessionRegistry.getAllSessions(user, true)) {
session.expireNow();
sessionRegistry.removeSessionInformation(session.getSessionId());
publisher.publishEvent(AskToExpireSessionEvent.of(session.getSessionId()));
expieredAtLeastOneSession = true;
}
break findPrincipal;
}
} else {
logger.warn("encountered a session for a none user object {} while invalidating '{}' " , principal, userName);
}
}
}
return expieredAtLeastOneSession;
}
}
申请事件:
import org.springframework.context.ApplicationEvent;
public class AskToExpireSessionEvent extends ApplicationEvent {
private static final long serialVersionUID = -1915691753338712193L;
public AskToExpireSessionEvent(final Object source) {
super(source);
}
@Override
public String getSource() {
return (String)super.getSource();
}
public static AskToExpireSessionEvent of(final String sessionId) {
return new AskToExpireSessionEvent(sessionId);
}
}
http 会话缓存侦听器:
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.web.session.HttpSessionCreatedEvent;
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
import org.springframework.stereotype.Component;
import com.cb4.base.service.event.AskToExpireSessionEvent;
@Component
public class HttpSessionCachingListener {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionCachingListener.class);
private final Map<String, HttpSession> sessionCache = new WeakHashMap<>();
void onHttpSessionCreatedEvent(final HttpSessionCreatedEvent event){
if (event != null && event.getSession() != null && event.getSession().getId() != null) {
sessionCache.put(event.getSession().getId(), event.getSession());
}
}
void onHttpSessionDestroyedEvent(final HttpSessionDestroyedEvent event){
if (event != null && event.getSession() != null && event.getSession().getId() != null){
sessionCache.remove(event.getSession().getId());
}
}
public void timeOutSession(final String sessionId){
if(sessionId != null){
final HttpSession httpSession = sessionCache.get(sessionId);
if(null != httpSession){
logger.debug("invalidating session {} in 1 second", sessionId);
httpSession.setMaxInactiveInterval(1);
}
}
}
@Component
static class HttpSessionCreatedLisener implements ApplicationListener<HttpSessionCreatedEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final HttpSessionCreatedEvent event) {
parent.onHttpSessionCreatedEvent(event);
}
}
@Component
static class HttpSessionDestroyedLisener implements ApplicationListener<HttpSessionDestroyedEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final HttpSessionDestroyedEvent event) {
parent.onHttpSessionDestroyedEvent(event);
}
}
@Component
static class AskToTimeOutSessionLisener implements ApplicationListener<AskToExpireSessionEvent> {
@Autowired
HttpSessionCachingListener parent;
@Override
public void onApplicationEvent(final AskToExpireSessionEvent event) {
if(event != null){
parent.timeOutSession(event.getSource());
}
}
}
}
使用 java 配置在 class 中添加以下代码扩展 WebSecurityConfigurerAdapter :
@Bean
public SessionRegistry sessionRegistry( ) {
SessionRegistry sessionRegistry = new SessionRegistryImpl( );
return sessionRegistry;
}
@Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}
并在您的 configure( HttpSecurity http ) 方法中添加以下内容:
http.sessionManagement( ).maximumSessions( -1 ).sessionRegistry( sessionRegistry( ) );
http.sessionManagement( ).sessionFixation( ).migrateSession( )
.sessionAuthenticationStrategy( registerSessionAuthStr( ) );
此外,在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy,如下所示:
usernamePasswordAuthenticationFilter
.setSessionAuthenticationStrategy( registerSessionAuthStr( ) );
注意:在您的自定义身份验证 bean 中设置 registerSessionAuthenticationStrategy 会导致填充主体列表,因此当您尝试从 sessionRegistry 获取所有主体的列表时(sessionRegistry.getAllPrinicpals() ),列表不为空。