【发布时间】:2015-12-14 06:46:17
【问题描述】:
我的要求是在 30 分钟不活动后使会话过期。
我的堆栈如下:
- JBoss 7.2
- Spring MVC 4.0.6
- Spring Security 3.2.4
一些相关信息:
- 预认证由 JBoss(LDAP 和 SPNEGO)完成
- Spring Security 在整个应用程序中用于授权目的。 SessionRegistry 工作正常,因为我有一个当前 HTTP 会话列表(CurrentSessionController.java 和一个 JSP),能够使现有会话过期。
- Java Config 是首选
问题是,如果用户闲置一段时间然后达到 web.xml 中定义的会话超时阈值,他仍然可以浏览应用程序。创建了一个新的 HttpSession,他仍然可以使用该应用程序。
我想要的是,一旦会话过期,用户就不能调用另一个请求处理程序(理想情况下,Spring Security 会像使用 sessionInformation.expireNow() 一样使会话过期)。用户的下一个操作(HTTP 请求)将重定向到特定的 JSP 页面。
web.xml(1 分钟用于测试目的)
<session-config>
<session-timeout>1</session-timeout>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
AppInitializer.java
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext applicationContext = this.getContext();
this.setServletFilters(servletContext);
this.setServletListeners(servletContext, applicationContext);
ServletRegistration.Dynamic dispatcherServlet = servletContext.addServlet("dispatcherServlet", new DispatcherServlet(applicationContext));
dispatcherServlet.setLoadOnStartup(1);
dispatcherServlet.addMapping("/rest/*");
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.scan("my.app.spring4base.config");
return context;
}
private void setServletFilters(ServletContext servletContext) {
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"));
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/rest/*");
FilterRegistration.Dynamic sessionFilter = servletContext.addFilter("sessionFilter", SessionFilter.class);
sessionFilter.addMappingForUrlPatterns(null, false, "/rest/*");
FilterRegistration.Dynamic requestContextFilter = servletContext.addFilter("requestContextFilter", RequestContextFilter.class);
requestContextFilter.addMappingForUrlPatterns(null, false, "/*");
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", CharacterEncodingFilter.class);
encodingFilter.addMappingForUrlPatterns(null, false, "/*");
encodingFilter.setInitParameters(new HashMap<String, String>() {{
put("encoding", StandardCharsets.UTF_8.name());
put("forceEncoding", "true");
}});
}
private void setServletListeners(ServletContext servletContext, WebApplicationContext applicationContext) {
servletContext.addListener(new ContextLoaderListener(applicationContext));
servletContext.addListener(new RequestContextListener());
servletContext.addListener(new SpringApplicationScopedBeanDeprefixer());
servletContext.addListener(new SpringSessionScopedBeanDeprefixer());
servletContext.addListener(new HttpSessionEventPublisher());
}
SecurityConfig.java
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public CustomPreAuthenticationFilter customPreAuthenticationFilter() {
return new CustomPreAuthenticationFilter();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(this.customPreAuthenticationFilter(), J2eePreAuthenticatedProcessingFilter.class)
.csrf().disable()
.logout()
.logoutUrl("/rest/logout")
.logoutSuccessUrl("/static/jsp/logout.jsp")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/login","/accessDenied", "/sessionTimeout", "/resources/**", "/static/**").permitAll()
.antMatchers("/security/*").hasRole("ADMIN")
.antMatchers("/rest/**").authenticated()
.antMatchers("/rest/**").hasRole("USER")
.and().anonymous().disable()
.jee()
.mappableRoles("USER","ADMIN", "DEVELOPER")
.and()
.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(true)
.expiredUrl("/static/jsp/sessionTimeout.jsp")
.and()
.invalidSessionUrl("/static/jsp/sessionInvalid.jsp")
.sessionFixation();
}
}
我也在使用 Spring 会话范围的目标 bean(会话对象模式),定义如下:
SessionObject.java
@Component
@Scope(
value = "session",
proxyMode = ScopedProxyMode.TARGET_CLASS
)
public class SessionObject {
private User currentUser;
public SessionObject() {
}
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User user) {
this.currentUser = user;
}
public boolean isConnected() {
return currentUser != null;
}
}
我曾尝试使用 Servlet 过滤器,然后检查我的 Spring 会话范围 bean 是否存在,但事实证明它始终可用。
SessionFilter.java
public class SessionFilter implements Filter {
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
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);
//Also tried the following:
//if (sessionObject == null) {
if (session.getAttribute("scopedTarget.sessionObject") == null) {
response.sendRedirect("/static/jsp/sessionTimeout.jsp");
} else {
chain.doFilter(request, response);
}
}
}
会话超时时来自 Spring Security 的日志
- 超时前的会话 ID 为:DnfCin+LXESn6QvKqd6jOlPe
- 超时(刷新)后的会话 ID 为:dcW8VDH7uBjmd9Ve4v4PoFHZ
会话超时
2015-09-17 08:32:56,586 DEBUG [org.springframework.security.web.session.HttpSessionEventPublisher] (http-/0.0.0.0:8080-2) Publishing event: org.springframework.security.web.session.HttpSessionDestroyedEvent[source=org.apache.catalina.session.StandardSessionFacade@61da4a]
2015-09-17 08:32:56,586 DEBUG [org.springframework.security.core.session.SessionRegistryImpl] (http-/0.0.0.0:8080-2) Removing session DnfCin+LXESn6QvKqd6jOlPe from principal's set of registered sessions
2015-09-17 08:32:56,586 DEBUG [org.springframework.security.core.session.SessionRegistryImpl] (http-/0.0.0.0:8080-2) Removing principal org.springframework.security.core.userdetails.User@4eb878aa: Username: MYUSERID@MY.DOMAIN; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_DEVELOPER,ROLE_USER from registry
会话超时后(创建新会话时)
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.session.HttpSessionEventPublisher] (http-/0.0.0.0:8080-2) Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@276f3c]
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy] (http-/0.0.0.0:8080-2) Started new session: dcW8VDH7uBjmd9Ve4v4PoFHZ
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy] (http-/0.0.0.0:8080-2) Delegating to org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy@708afe
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.core.session.SessionRegistryImpl] (http-/0.0.0.0:8080-2) Registering session dcW8VDH7uBjmd9Ve4v4PoFHZ, for principal org.springframework.security.core.userdetails.User@4eb878aa: Username: MYUSERID@MY.DOMAIN; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_DEVELOPER,ROLE_USER
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (http-/0.0.0.0:8080-2) SecurityContext stored to HttpSession: 'org.springframework.security.core.context.SecurityContextImpl@139f29a9: Authentication: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@139f29a9: Principal: org.springframework.security.core.userdetails.User@4eb878aa: Username: MYUSERID@MY.DOMAIN; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_DEVELOPER,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails@380f4: RemoteIpAddress: 127.0.0.1; SessionId: OUp-h8jL7jI0wIfGUEB9mgX8; [ROLE_ADMIN, ROLE_USER, ROLE_DEVELOPER]; Granted Authorities: ROLE_ADMIN, ROLE_DEVELOPER, ROLE_USER'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.FilterChainProxy] (http-/0.0.0.0:8080-2) /rest/welcome at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.FilterChainProxy] (http-/0.0.0.0:8080-2) /rest/welcome at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/rest/logout'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/login'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/accessdenied'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/sessiontimeout'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/resources/**'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/static/**'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/security/*'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (http-/0.0.0.0:8080-2) Checking match of request : '/rest/welcome'; against '/rest/**'
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (http-/0.0.0.0:8080-2) Secure object: FilterInvocation: URL: /rest/welcome; Attributes: [hasRole('ROLE_USER')]
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (http-/0.0.0.0:8080-2) Previously Authenticated: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@139f29a9: Principal: org.springframework.security.core.userdetails.User@4eb878aa: Username: MYUSERID@MY.DOMAIN; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_DEVELOPER,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails@380f4: RemoteIpAddress: 127.0.0.1; SessionId: OUp-h8jL7jI0wIfGUEB9mgX8; [ROLE_ADMIN, ROLE_USER, ROLE_DEVELOPER]; Granted Authorities: ROLE_ADMIN, ROLE_DEVELOPER, ROLE_USER
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.access.vote.AffirmativeBased] (http-/0.0.0.0:8080-2) Voter: org.springframework.security.web.access.expression.WebExpressionVoter@1f8c768, returned: 1
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (http-/0.0.0.0:8080-2) Authorization successful
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (http-/0.0.0.0:8080-2) RunAsManager did not change Authentication object
2015-09-17 08:32:59,958 DEBUG [org.springframework.security.web.FilterChainProxy] (http-/0.0.0.0:8080-2) /rest/welcome reached end of additional filter chain; proceeding with original chain
2015-09-17 08:32:59,958 DEBUG [org.springframework.web.filter.RequestContextFilter] (http-/0.0.0.0:8080-2) Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.context.HttpSessionSecurityContextRepository$Servlet3SaveToSessionRequestWrapper@f7ef18]
2015-09-17 08:32:59,958 DEBUG [org.springframework.web.servlet.DispatcherServlet] (http-/0.0.0.0:8080-2) DispatcherServlet with name 'dispatcherServlet' processing GET request for [/spring4base/rest/welcome]
2015-09-17 08:32:59,958 DEBUG [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] (http-/0.0.0.0:8080-2) Looking up handler method for path /welcome
2015-09-17 08:32:59,958 DEBUG [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] (http-/0.0.0.0:8080-2) Returning handler method [public java.lang.String my.app.spring4base.web.controller.WelcomeController.welcomePage()]
2015-09-17 08:32:59,958 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] (http-/0.0.0.0:8080-2) Returning cached instance of singleton bean 'welcomeController'
任何帮助将不胜感激。
谢谢
【问题讨论】:
-
我会说问题出在您使用的是
CustomPreAuthenticationFilter和可能的J2eePreAuthenticatedProcessingFilter,因为这基本上会重新验证用户身份(我怀疑)。签入SessionFilter将失败,尤其是在您的情况下,因为它是在 Spring Security 过滤器之后定义的(因此它将存在)。其他线程/请求无法使HttpSession过期,因为这基本上是一种安全违规(您会觉得有人可以访问您的会话状态!)。 -
尝试将 org.springframework.security 置于调试级别日志记录 - Spring security 通常具有非常好的日志记录,并且您怀疑的事件(会话无效和重新创建)很有可能出现在日志中.
-
@Shailendra:我已将日志添加到问题中。谢谢!
-
不确定你的
CustomPreAuthenticationFilter做了什么,但也许你想让它更聪明一点(或者实际上是J2eePreAuthenticatedProcessingFilter)你可以让它更聪明一点,当检测到无效会话时不进行身份验证但不确定在这种情况下会出现什么问题。 -
由于 J2eePreAuthenticatedProcessingFilter 正在重新验证用户并填充主体,并且会话固定或会话过滤器认为没有发生会话失效,因为有新的主体并盲目地创建新会话。因此,您可以尝试在会话失效时将用户更新为已过期,并且在预身份验证时检查是否用户已过期,不再允许进行身份验证..(即使在会话超时后有记住 cookie 的情况下也可以重新进行身份验证,类似于 persistent_login 的想法)
标签: spring spring-mvc spring-security jboss7.x servlet-3.0