原作地址:http://blog.csdn.net/xiejx618/article/details/42892951

参考资料:spring-security-reference.pdf的Session Management.特别是Concurrency Control小节.
管理session可以做到:
a.跟踪活跃的session,统计在线人数,显示在线用户.
b.控制并发,即一个用户最多可以使用多少个session登录,比如设为1,结果就为,同一个时间里,第二处登录要么不能登录,要么使前一个登录失效.

1.注册自定义的SessionRegistry(通过它可以做到上面的a点)

  1. @Bean
  2. public SessionRegistry sessionRegistry(){
  3. return new SessionRegistryImpl();
  4. }

2.使用session并发管理,并注入上面自定义的SessionRegistry

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http.authorizeRequests().anyRequest().authenticated()
  4. .and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll()
  5. .and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout").permitAll()
  6. .and().rememberMe().key("9D119EE5A2B7DAF6B4DC1EF871D0AC3C")
  7. .and().exceptionHandling().accessDeniedPage("/exception/403")
  8. .and().sessionManagement().maximumSessions(2).expiredUrl("/login?expired").sessionRegistry(sessionRegistry());
  9. }

3.监听session创建和销毁的HttpSessionListener.让spring security更新有关会话的生命周期,实现上创建的监听只使用销毁事件,至于session创建,security是调用org.springframework.security.core.session.SessionRegistry#registerNewSession
针对servlet管理的session,应使用org.springframework.security.web.session.HttpSessionEventPublisher,方法有多种:
a.重写org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer#enableHttpSessionEventPublisher

  1. public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
  2. @Override
  3. protected boolean enableHttpSessionEventPublisher() {
  4. return true;
  5. }
  6. }
b.在AbstractAnnotationConfigDispatcherServletInitializer的子类DispatcherServletInitializer添加
  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. super.onStartup(servletContext);
  4. FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encoding-filter", CharacterEncodingFilter.class);
  5. encodingFilter.setInitParameter("encoding", "UTF-8");
  6. encodingFilter.setInitParameter("forceEncoding", "true");
  7. encodingFilter.setAsyncSupported(true);
  8. encodingFilter.addMappingForUrlPatterns(null, false, "/*");
  9. servletContext.addListener(new HttpSessionEventPublisher());
  10. }
使用springSession,直接向servletContext添加的session销毁监听是没用的,看springSession的文档http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-httpsessionlistener,将org.springframework.security.web.session.HttpSessionEventPublisher注册成Bean就可以了.它的底层是对springSession的创建和销毁进行监听,不一样的.
还要注意的是,添加对HttpSessionListener的支持是从spring Session 1.1.0开始的,写这博文的时候,这版本还没出来.所以,以前的源码有问题.
  1. @Configuration
  2. @EnableRedisHttpSession
  3. @PropertySource("classpath:config.properties")
  4. public class HttpSessionConfig {
  5. @Resource
  6. private Environment env;
  7. @Bean
  8. public JedisConnectionFactory jedisConnectionFactory() {
  9. JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
  10. connectionFactory.setHostName(env.getProperty("redis.host"));
  11. connectionFactory.setPort(env.getProperty("redis.port",Integer.class));
  12. return connectionFactory;
  13. }
  14. @Bean
  15. public HttpSessionEventPublisher httpSessionEventPublisher() {
  16. return new HttpSessionEventPublisher();
  17. }
  18. }
4.在spring controller注入SessionRegistry,测试.

附加session的创建与销毁分析:
至于session的创建比较简单,认证成功后,security直接调用
  1. org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter{
  2. sessionStrategy.onAuthentication(authResult, request, response);
  3. }
  4. org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy#onAuthentication{
  5. sessionRegistry.registerNewSession(request.getSession().getId(), uthentication.getPrincipal());
  6. }
session的销毁.没有特殊修改,org.springframework.security.web.authentication.logout.LogoutFilter#handlers只有一个元素org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler,如果主动logout,就会触发org.springframework.security.web.authentication.logout.LogoutFilter#doFilter,进而调用org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler#logout,从这个方法可以看出别人是怎么处理失效的session的
  1. public void logout(HttpServletRequest request, HttpServletResponse response,
  2. Authentication authentication) {
  3. Assert.notNull(request, "HttpServletRequest required");
  4. if (invalidateHttpSession) {
  5. HttpSession session = request.getSession(false);
  6. if (session != null) {
  7. logger.debug("Invalidating session: " + session.getId());
  8. session.invalidate();
  9. }
  10. }
  11. if (clearAuthentication) {
  12. SecurityContext context = SecurityContextHolder.getContext();
  13. context.setAuthentication(null);
  14. }
  15. SecurityContextHolder.clearContext();
  16. }
这里可以看到使session失效,调用SecurityContextHolder.getContext().setAuthentication(null),清理SecurityContext
spring security登出操作和session过期都会引起session被销毁.就会触发org.springframework.security.web.session.HttpSessionEventPublisher#sessionDestroyed事件.源码如下
  1. public void sessionDestroyed(HttpSessionEvent event) {
  2. HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession());
  3. Log log = LogFactory.getLog(LOGGER_NAME);
  4. if (log.isDebugEnabled()) {
  5. log.debug("Publishing event: " + e);
  6. }
  7. getContext(event.getSession().getServletContext()).publishEvent(e);
  8. }
getContext(event.getSession().getServletContext())得到的是Root ApplicationContext,所以要把SessionRegistryImpl Bean注册到Root ApplicationContext,这样SessionRegistryImpl的onApplicationEvent方法才能接收上面发布的HttpSessionDestroyedEvent事件.
  1. public void onApplicationEvent(SessionDestroyedEvent event) {
  2. String sessionId = event.getId();
  3. removeSessionInformation(sessionId);
  4. }
这里就看removeSessionInformation(sessionId);这里就会对SessionRegistryImpl相关信息进会更新.进而通过SessionRegistryImpl获得那些用户登录了,一个用户有多少个SessionInformation都进行了同步.

再来讨论getContext(event.getSession().getServletContext())
  1. ApplicationContext getContext(ServletContext servletContext) {
  2. return SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(servletContext);
  3. }
  4. public static WebApplicationContext findRequiredWebApplicationContext(ServletContext servletContext) {
  5. WebApplicationContext wac = _findWebApplicationContext(servletContext);
  6. if (wac == null) {
  7. throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
  8. }
  9. return wac;
  10. }
  11. private static WebApplicationContext _findWebApplicationContext(ServletContext sc) {
  12. //从下面调用看,得到的是Root ApplicationContext,而不是Servlet ApplicationContext
  13. WebApplicationContext wac = getWebApplicationContext(sc);
  14. if (wac == null) {
  15. Enumeration<String> attrNames = sc.getAttributeNames();
  16. while (attrNames.hasMoreElements()) {
  17. String attrName = attrNames.nextElement();
  18. Object attrValue = sc.getAttribute(attrName);
  19. if (attrValue instanceof WebApplicationContext) {
  20. if (wac != null) {
  21. throw new IllegalStateException("No unique WebApplicationContext found: more than one " +
  22. "DispatcherServlet registered with publishContext=true?");
  23. }
  24. wac = (WebApplicationContext) attrValue;
  25. }
  26. }
  27. }
  28. return wac;
  29. }
  30. public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
  31. return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
  32. }
再假设得到的Servlet ApplicationContext,它还有parent(Root ApplicationContext),那么它也会通知Root ApplicationContext下监听SessionDestroyedEvent事件的Bean,(哈哈,但是没有那么多的如果);
但我还要如果用户就想在servlet注册SessionRegistryImpl,我觉得你可以继承HttpSessionEventPublisher,重写getContext方法了 针对于servlet容器的session,至于session过期,如果想测试,可以去改一下session的有效期短一点,然后等待观察.下面是我的测试web.xml全部内容
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app
  3. xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  6. metadata-complete="true"
  7. version="3.1">
  8. <session-config>
  9. <session-timeout>3</session-timeout>
  10. </session-config>
  11. </web-app>
对于用户主动关闭浏览器,服务端是没有马上触发sessionDestroyed的,等待session过期应该是大多数开发者的需求.

关于踢下线功能:使用org.springframework.security.core.session.SessionRegistry#getAllSessions就可以得到某个用户的所有SessionInformation,SessionInformation当然包括sessionId,剩下的问题就是根据sessionId获取session,再调用session.invalidate()就可以完成需求了.但是javax.servlet.http.HttpSessionContext#getSession已过期,并且因为安全原因没有替代方案,所以从servlet api2.1以后的版本,此路是不通的.
spring security提供了org.springframework.security.core.session.SessionInformation#expireNow,它只是标志了一下过期,直到下次用户请求被org.springframework.security.web.session.ConcurrentSessionFilter#doFilter拦截,
  1. HttpSession session = request.getSession(false);
  2. if (session != null) {
  3. SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
  4. if (info != null) {
  5. if (info.isExpired()) {
  6. // Expired - abort processing
  7. doLogout(request, response);
  8. //其它代码忽略
  9. }
  10. }
  11. }
这里就会触发了用户登出.还有一种思路,session保存在redis,直接从redis删除某个session数据,详细看org.springframework.session.SessionRepository,不太推荐这么干.

还有SessionRegistryImpl实现的并发控制靠以下两个变量实现的用户在线列表,重启应用这两个实例肯定会销毁,
/** <principal:Object,SessionIdSet> */
private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap<Object, Set<String>>();
/** <sessionId:Object,SessionInformation> */
private final Map<String, SessionInformation> sessionIds = new ConcurrentHashMap<String, SessionInformation>(); 既然分布式应用也会有问题,这时就要实现自己的SessionRegistry,将session的信息应保存到一个集中的地方进行管理.

基于java config的springSecurity--session并发控制的更多相关文章

  1. Spring Web工程web.xml零配置即使用Java Config + Annotation

    摘要: 在Spring 3.0之前,我们工程中常用Bean都是通过XML形式的文件注解的,少了还可以,但是数量多,关系复杂到后期就很难维护了,所以在3.x之后Spring官方推荐使用Java Conf ...

  2. Java Config 注解

    java config是指基于java配置的spring.传统的Spring一般都是基本xml配置的,后来spring3.0新增了许多java config的注解,特别是spring boot,基本都 ...

  3. 2510-Druid监控功能的深入使用与配置-基于SpringBoot-完全使用java config的形式

    环境 springboot 1.5.9.RELEASE + JDK1.8 配置步骤 分两步,1 配置数据源 2 配置监控 直接上代码 1 配置数据源 package com.company.proje ...

  4. Spring IOC之基于JAVA的配置

    基础内容:@Bean 和 @Configuration 在Spring中新的支持java配置的核心组件是 @Configuration注解的类和@Bean注解的方法. @Bean注解被用于表明一个方法 ...

  5. Spring Security4实例(Java config 版) —— Remember-Me

    本文源码请看这里 相关文章: Spring Security4实例(Java config版)--ajax登录,自定义验证 Spring Security提供了两种remember-me的实现,一种是 ...

  6. Spring学习(15)--- 基于Java类的配置Bean 之 @Bean & @Scope 注解

    默认@Bean是单例的,但可以使用@Scope注解来覆盖此如下: @Configuration public class MyConfiguration { @Bean @Scope("pr ...

  7. Spring Security4实例(Java config版)——ajax登录,自定义验证

    本文源码请看这里 相关文章: Spring Security4实例(Java config 版) -- Remember-Me 首先添加起步依赖(如果不是springboot项目,自行切换为Sprin ...

  8. (4.1)Spring MVC执行原理和基于Java的配置过程

    一.Spring MVC执行原理和基于Java配置的配置过程 (一)Spring MVC执行过程,大致为7步. 所有的请求都会经过Spring的一个单例的DispacherServlet. Dispa ...

  9. 基于Java的WebSocket推送

    WebSocket的主动推送 关于消息推送,现在的解决方案如轮询.长连接或者短连接,当然还有其他的一些技术框架,有的是客户端直接去服务端拿数据. 其实推送推送主要讲的是一个推的概念,WebSocket ...

随机推荐

  1. sed命令替换文件内容

    reference: https://www.cnblogs.com/starof/p/4181985.html 抓取目录名并修改 ls | grep "XXX" > 1.t ...

  2. 【hdu 6155】Subsequence Count

    题意 给定一个 \(01\) 串 \(S_{1...n}\) 和 \(Q\) 个操作. 操作有 \(2\) 种类型: 1. 将 \([l,r]\) 区间所有数取反(\(0→1,\space 1→0\) ...

  3. 【AGC 036C】GP2

    https://atcoder.jp/contests/agc036/tasks/agc036_c 题意 有一个长度为 $n$ 的非负整数序列 $x$,初始时全为 $0$.一次操作定义为选择一对正整数 ...

  4. 清北学堂dp图论营游记day3

    .状态压缩dp: 对于这个我们引入二进制状态压缩,因为任何一个数都可以二进制表示,而其二进制表示上每一位都可以表示当前位置是否有元素,这就构成了状态压缩. 对于这个题,上下行&一下就行. 状压 ...

  5. jmeter中的几种参数化场景

    1.request路径中引用参数 2.body中引用参数 3.parameter中引用参数 4.header中引用参数,如token这类跟用户相关参数 5.response assertion中引用参 ...

  6. CentOS 6的系统启动流程

    一.POST加电自检 按下电源后ROM芯片中的CMOS程序执行并检测CPU.内存等设备是否存在并正常运行,CMOS中的程序叫BIOS,可以设置硬盘接口,网卡声卡开关之类的简单设置.一般PC机主板上有一 ...

  7. php的$_get,$_post用法

    $_GET 可以被收藏, 可以被缓存, 可以保存在历史记录中, 可以提交请求但是很不安全, 长度有限制在2000个字符,其实get请求就是一个url;$_GET['user_name'] $_POST ...

  8. js 选中文字

    选中文字,文字背景是蓝色 当前点击的元素: var e = e || event; var tag = e.target || e.srcElement; 选中文字:window.getSelecti ...

  9. jquery result属性 语法

    jquery result属性 语法 作用:result 属性包含由被指定事件触发的事件处理器返回的最后一个值,除非这个值未定义.大理石平台精度等级 语法:event.resul 参数: 参数 描述 ...

  10. Generalizing from a Few Examples: A Survey on Few-Shot Learning(从几个例子总结经验:少样本学习综述)

    摘要:人工智能在数据密集型应用中取得了成功,但它缺乏从有限的示例中学习的能力.为了解决这一问题,提出了少镜头学习(FSL).利用先验知识,可以快速地从有限监督经验的新任务中归纳出来.为了全面了解FSL ...