JavaEE容器(JBoss)中的Spring安全性和会话超时
我有一个要求,那就是在30分钟不活动后,会话将过期 我的堆栈如下所示:JavaEE容器(JBoss)中的Spring安全性和会话超时,spring,spring-mvc,spring-security,jboss7.x,servlet-3.0,Spring,Spring Mvc,Spring Security,Jboss7.x,Servlet 3.0,我有一个要求,那就是在30分钟不活动后,会话将过期 我的堆栈如下所示: JBoss7.2 SpringMVC4.0.6 Spring Security 3.2.4 一些相关信息: 预认证由JBoss(LDAP和SPNEGO)完成 Spring Security用于整个应用程序的授权目的。SessionRegistry工作正常,因为我得到了一个当前HTTP会话列表(CurrentSessionController.java和一个JSP),可以使现有会话过期 首选Java配置 问题是,如果用
- JBoss7.2
- SpringMVC4.0.6
- Spring Security 3.2.4
- 预认证由JBoss(LDAP和SPNEGO)完成
- Spring Security用于整个应用程序的授权目的。SessionRegistry工作正常,因为我得到了一个当前HTTP会话列表(CurrentSessionController.java和一个JSP),可以使现有会话过期
- 首选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());
}
@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();
}
@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;
}
}
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);
}
}
}
我尝试使用Servlet过滤器,然后检查我的Spring会话范围bean是否存在,但结果证明它总是可用的
SessionFilter.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());
}
@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();
}
@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;
}
}
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记录日志
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
- 超时前的会话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
会话超时后(创建新会话时)
任何帮助都将不胜感激
谢谢你将上述评论转换为一条建议
由于J2eePreAuthenticatedProcessingFilter正在重新验证用户并填充主体,会话固定或会话筛选器认为没有发生会话失效,因为存在新主体并盲目创建新会话。因此,您可以尝试在会话失效时将用户更新为已过期,并在预身份验证时检查用户是否已过期,是否不再允许进行身份验证…(例如,即使在会话超时后有记住cookie的情况下,也可以使用持久登录想法重新身份验证)以上注释转换为一个建议
由于J2eePreAuthenticatedProcessingFilter正在重新验证用户并填充主体,会话固定或会话筛选器认为没有发生会话失效,因为存在新主体并盲目创建新会话。因此,您可以尝试在会话失效时将用户更新为已过期,并在预身份验证时检查用户是否已过期,是否不再允许进行身份验证…(例如,即使在会话超时后有RememberCookie,也可以使用persistent_login idea重新身份验证)我想说,问题是因为您使用的是
CustomPreAuthenticationFilter
,可能是J2EEPreAuthenticationdProcessingFilter
,因为这基本上会重新验证用户(我怀疑)。签入SessionFilter
将失败,尤其是在您的情况下,因为它是在Spring安全过滤器之后定义的(因此它将出现)。无法从其他线程/请求中终止HttpSession
,因为这基本上是一种安全违规行为(如果有人可以访问您的会话状态,您会有什么感觉!)。请尝试将org.springframework.security设置为调试级日志记录-Spring security通常有很好的日志记录,并且您怀疑的事件(会话无效和重新创建)很有可能出现在日志中。@Shailendra:我已将日志添加到问题中。非常感谢。不确定您的CustomPreAuthenticationFilter
做了什么,但可能您想让它更智能一点(或者实际上是J2EEPreAuthenticationdProcessingFilter
)当检测到无效会话时,您可以更明智地不进行身份验证,但不确定在这种情况下会出现什么中断。因为J2eePreAuthenticatedProcessingFilter正在重新验证用户并填充主体,而会话固定或会话筛选器认为没有发生会话无效,因为存在新的主体和盲目地创建新会话。因此,您可以尝试在会话失效时将用户更新为已过期,并在预身份验证时检查用户是否已过期,是否不再允许进行身份验证…(例如,即使在会话超时后有RememberCookie,也可以使用persistent_login idea重新身份验证)我想说的是,问题来自这样一个事实:您使用的是CustomPreAuthenticationFilter
,可能是J2EEPreAuthenticationdProcessingFilter
,因为这基本上会重新验证用户(我怀疑)。签入SessionFilter
将失败,尤其是在您的情况下,因为它是在Spring安全过滤器之后定义的(因此它将出现)。无法从其他线程/请求中终止HttpSession
,因为这基本上是一种安全违规行为(如果有人可以访问您的会话状态,您会有什么感觉!)。请尝试将org.springframework.security设置为调试级日志记录-Spring security通常有很好的日志记录,并且您怀疑的事件(会话无效和重新创建)很有可能出现在日志中。@Shailendra:我已将日志添加到问题中。非常感谢。不确定您的CustomPreAuthenticationFilter
做了什么,但可能您想让它更智能一点(或者实际上是J2EEPreAuthenticationdProcessingFilter
),您可以让它更智能一点,在