对于spring security我个人是比较喜欢的一个安全框架,我们的系统中一般需要提供强制将用户踢出的功能,这个功能security也有提供,
首先我们要操作需要获取sessionRegistry中认证用户的所有SessionInformation,然后逐个调用SessionInformation里的expireNow()方法,然后ConcurrentSessionFilter
就会执行用户登录注销的功能;对于这个sessionRegistry.removeSessionInformation(sessionInformation
.getSessionId());我不知道是不是我的用法不对,调用了也不会强制注销用户的session,因为官方源代码中说调用removeSessionInformation的时候会让session的监听器响应到,但是我是没成功(望使用这个方式成功的朋友留言告诉下我);下面我们就看为什么调用SessionInformation里的expireNow()方法就能注销用户
-
-
-
- public void shotOff() {
- List<SessionInformation> sessionInformations = sessionRegistry
- .getAllSessions(SpringSecurityManager.getAuthentication()
- .getPrincipal(), false);
- for (SessionInformation sessionInformation : sessionInformations) {
- sessionInformation.expireNow();
-
-
- .getSessionId());
-
- }
- }
我们来看下SessionInformation的源码
- private Date lastRequest;
- private final Object principal;
- private final String sessionId;
- private boolean expired = false;
-
-
-
- public SessionInformation(Object principal, String sessionId, Date lastRequest) {
- Assert.notNull(principal, "Principal required");
- Assert.hasText(sessionId, "SessionId required");
- Assert.notNull(lastRequest, "LastRequest required");
- this.principal = principal;
- this.sessionId = sessionId;
- this.lastRequest = lastRequest;
- }
-
-
-
- public void expireNow() {
- this.expired = true;
- }
-
- public Date getLastRequest() {
- return lastRequest;
- }
-
- public Object getPrincipal() {
- return principal;
- }
-
- public String getSessionId() {
- return sessionId;
- }
-
- public boolean isExpired() {
- return expired;
- }
-
-
-
-
- public void refreshLastRequest() {
- this.lastRequest = new Date();
- }
看到的是调用expireNow方法时候会将属性expired设置为true;
然后我们来看下需要处理的ConcurrentSessionFilter过滤器的源码
- private SessionRegistry sessionRegistry;
- private String expiredUrl;
- private LogoutHandler[] handlers = new LogoutHandler[] {new SecurityContextLogoutHandler()};
- private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
-
-
-
-
-
-
-
- public ConcurrentSessionFilter() {
- }
-
- public ConcurrentSessionFilter(SessionRegistry sessionRegistry) {
- this(sessionRegistry, null);
- }
-
- public ConcurrentSessionFilter(SessionRegistry sessionRegistry, String expiredUrl) {
- this.sessionRegistry = sessionRegistry;
- this.expiredUrl = expiredUrl;
- }
-
- @Override
- public void afterPropertiesSet() {
- Assert.notNull(sessionRegistry, "SessionRegistry required");
- Assert.isTrue(expiredUrl == null || UrlUtils.isValidRedirectUrl(expiredUrl),
- expiredUrl + " isn't a valid redirect URL");
- }
-
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
- throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
-
- HttpSession session = request.getSession(false);
-
- if (session != null) {
- SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
-
- if (info != null) {
- if (info.isExpired()) {
-
- doLogout(request, response);
-
- String targetUrl = determineExpiredUrl(request, info);
-
- if (targetUrl != null) {
- redirectStrategy.sendRedirect(request, response, targetUrl);
-
- return;
- } else {
- response.getWriter().print("This session has been expired (possibly due to multiple concurrent " +
- "logins being attempted as the same user).");
- response.flushBuffer();
- }
-
- return;
- } else {
-
- sessionRegistry.refreshLastRequest(info.getSessionId());
- }
- }
- }
-
- chain.doFilter(request, response);
- }
-
- protected String determineExpiredUrl(HttpServletRequest request, SessionInformation info) {
- return expiredUrl;
- }
-
- private void doLogout(HttpServletRequest request, HttpServletResponse response) {
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
-
- for (LogoutHandler handler : handlers) {
- handler.logout(request, response, auth);
- }
- }
-
-
-
-
- @Deprecated
- public void setExpiredUrl(String expiredUrl) {
- this.expiredUrl = expiredUrl;
- }
-
-
-
-
- @Deprecated
- public void setSessionRegistry(SessionRegistry sessionRegistry) {
- this.sessionRegistry = sessionRegistry;
- }
-
- public void setLogoutHandlers(LogoutHandler[] handlers) {
- Assert.notNull(handlers);
- this.handlers = handlers;
- }
-
- public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
- this.redirectStrategy = redirectStrategy;
- }
很明显代码中有遇到if (info.isExpired())逻辑判断,这个时候如果表达式的值为true就会执行里面的doLogout方法,而这个方法里面就会调用我们配置的注销监听器,当前用户在之前认证成功后的状态也会失效了
然后看下xml里怎么配置
- <!-- SESSION管理 -->
- <bean id="sessionRegistry"
- class="org.springframework.security.core.session.SessionRegistryImpl" />
-
- <bean id="concurrentSessionFilter"
- class="org.springframework.security.web.session.ConcurrentSessionFilter">
- <property name="sessionRegistry" ref="sessionRegistry" />
- <property name="expiredUrl" value="/apply/skip/restimeout.html" />
- <property name="logoutHandlers">
- <list>
- <ref local="logoutHandler" />
- </list>
- </property>
- </bean>
-
- <!-- 注销监听器 -->
- <bean id="logoutHandler"
- class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
- <property name="InvalidateHttpSession" value="true" />
- </bean>
==================================================================================================================
ps:我的实践配置(如下图1)
如果配置的sessionStrategy(session策略不生效,则需要重写UsernamePasswordAuthenticationFilter类的doFilter方法启用新配置ConcurrentSessionControlStrategy的session策略)(如下图2)
如果是集群环境下,还需要考虑session共享的问题,上面实现的是单机的session共享,session是存在内存中的,因此如果是集群环境,则需要将session放到redis中进行共享,此时需要重写SessionRegistryImpl类,来实现get和set session都在redis中。参考:http://www.imooc.com/article/4781,或者更简单的方法时修改tomcat content.xml文件的配置来实现session在集群上的共享,可参考http://blog.csdn.net/lipc_/article/details/52766884