无法使用自定义过滤器在 Spring 安全性中实施会话限制
Not able to implement session limiting in Spring Security with custom Filter
我的要求是限制多用户登录,在应用程序中一次只允许一个用户登录。
我的应用程序是一个 spring 启动应用程序,具有用于用户授权和身份验证的 JWT 身份验证。
我已经阅读了几篇文章,并且了解到可以在 spring 引导中使用 spring 安全性来实现。
package com.cellpointmobile.mconsole.security;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JWTAuthEntryPoint unauthorizedHandler;
@Bean
public JWTAuthenticationTokenFilter authenticationJwtTokenFilter() {
return new JWTAuthenticationTokenFilter();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
{
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Bean
public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/mconsole/app/login").permitAll()
.antMatchers("/mconsole/**").authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).maximumSessions(1).
maxSessionsPreventsLogin(true);
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.apply(customConfigurer());
}
@Bean
public CustomJwtAuthenticationTokenConfigurer customConfigurer() {
return new CustomJwtAuthenticationTokenConfigurer();
}
}
这是我的配置 class,我在其中添加了所需的代码。
还添加了一个新的 class 如下:
public class CustomJwtAuthenticationTokenConfigurer extends
AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
@Autowired
private JWTAuthenticationTokenFilter myFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
并在 JWTAuthenticationTokenFilter 中扩展了 AbstractAuthenticationProcessingFilter。
public class JWTAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter
{
@Autowired
private JWTProvider tokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationTokenFilter.class);
public JWTAuthenticationTokenFilter() {super("/mconsole/**"); }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("x-cpm-auth-token");
if (header == null) {
filterChain.doFilter(request, response);
return;
}
String sToken = header.replace("x-cpm-auth-token", "");
try {
if (sToken != null && tokenProvider.validateJwtToken(sToken, userDetailsService)) {
Authentication authResult;
UsernamePasswordAuthenticationToken authentication;
String sUserName = tokenProvider.getUserNameFromJwtToken(sToken, userDetailsService);
UserDetails userDetails = userDetailsService.loadUserByUsername(sUserName);
if (userDetails != null) {
authentication
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("entered in authentication>> " + authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new AuthenticationCredentialsNotFoundException("Could not createAuthentication user");
} try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} catch (AuthenticationException failed) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
} catch (Exception e) {
logger.error("Access denied !! Unable to set authentication", e);
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null)
throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return authentication;
}
@Override
@Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
我仍然可以多次登录,自 10 天以来我一直在努力实现这一目标,@Eleftheria Stein-Kousathana 你能检查一下我现在做错了什么吗?
首先,让我们考虑一下在使用表单登录的应用程序中,并发会话控制在没有自定义过滤器的简单情况下是如何工作的。
有效的登录请求将到达 UsernamePasswordAuthenticationFilter
。
在此过滤器中,通过调用 SessionAuthenticationStrategy#onAuthentication
.
检查会话并发限制
如果未超过最大限制,则用户登录成功。如果超过限制,则抛出 SessionAuthenticationException
一个 returns 对用户的错误响应。
要在自定义过滤器中具有相同的行为,您需要确保在 doFilter
方法中调用了 SessionAuthenticationStrategy#onAuthentication
。
实现此目的的一种方法是创建自定义配置器并将其应用到 HttpSecurity
配置中。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(customConfigurer())
// ...
}
public static class CustomJwtAuthenticationTokenConfigurer extends
AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
CustomJwtAuthenticationTokenFilter myFilter = new CustomJwtAuthenticationTokenFilter();
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public static CustomJwtAuthenticationTokenConfigurer customConfigurer() {
return new CustomJwtAuthenticationTokenConfigurer();
}
}
这假设 CustomJwtAuthenticationTokenFilter
扩展了 AbstractAuthenticationProcessingFilter
,但即使没有扩展,配置也会相似。
我的要求是限制多用户登录,在应用程序中一次只允许一个用户登录。 我的应用程序是一个 spring 启动应用程序,具有用于用户授权和身份验证的 JWT 身份验证。 我已经阅读了几篇文章,并且了解到可以在 spring 引导中使用 spring 安全性来实现。
package com.cellpointmobile.mconsole.security;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JWTAuthEntryPoint unauthorizedHandler;
@Bean
public JWTAuthenticationTokenFilter authenticationJwtTokenFilter() {
return new JWTAuthenticationTokenFilter();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
{
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Bean
public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/mconsole/app/login").permitAll()
.antMatchers("/mconsole/**").authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).maximumSessions(1).
maxSessionsPreventsLogin(true);
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.apply(customConfigurer());
}
@Bean
public CustomJwtAuthenticationTokenConfigurer customConfigurer() {
return new CustomJwtAuthenticationTokenConfigurer();
}
}
这是我的配置 class,我在其中添加了所需的代码。 还添加了一个新的 class 如下:
public class CustomJwtAuthenticationTokenConfigurer extends
AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
@Autowired
private JWTAuthenticationTokenFilter myFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
并在 JWTAuthenticationTokenFilter 中扩展了 AbstractAuthenticationProcessingFilter。
public class JWTAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter
{
@Autowired
private JWTProvider tokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationTokenFilter.class);
public JWTAuthenticationTokenFilter() {super("/mconsole/**"); }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("x-cpm-auth-token");
if (header == null) {
filterChain.doFilter(request, response);
return;
}
String sToken = header.replace("x-cpm-auth-token", "");
try {
if (sToken != null && tokenProvider.validateJwtToken(sToken, userDetailsService)) {
Authentication authResult;
UsernamePasswordAuthenticationToken authentication;
String sUserName = tokenProvider.getUserNameFromJwtToken(sToken, userDetailsService);
UserDetails userDetails = userDetailsService.loadUserByUsername(sUserName);
if (userDetails != null) {
authentication
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("entered in authentication>> " + authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new AuthenticationCredentialsNotFoundException("Could not createAuthentication user");
} try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} catch (AuthenticationException failed) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
} catch (Exception e) {
logger.error("Access denied !! Unable to set authentication", e);
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null)
throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return authentication;
}
@Override
@Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
我仍然可以多次登录,自 10 天以来我一直在努力实现这一目标,@Eleftheria Stein-Kousathana 你能检查一下我现在做错了什么吗?
首先,让我们考虑一下在使用表单登录的应用程序中,并发会话控制在没有自定义过滤器的简单情况下是如何工作的。
有效的登录请求将到达 UsernamePasswordAuthenticationFilter
。
在此过滤器中,通过调用 SessionAuthenticationStrategy#onAuthentication
.
检查会话并发限制
如果未超过最大限制,则用户登录成功。如果超过限制,则抛出 SessionAuthenticationException
一个 returns 对用户的错误响应。
要在自定义过滤器中具有相同的行为,您需要确保在 doFilter
方法中调用了 SessionAuthenticationStrategy#onAuthentication
。
实现此目的的一种方法是创建自定义配置器并将其应用到 HttpSecurity
配置中。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(customConfigurer())
// ...
}
public static class CustomJwtAuthenticationTokenConfigurer extends
AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
CustomJwtAuthenticationTokenFilter myFilter = new CustomJwtAuthenticationTokenFilter();
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public static CustomJwtAuthenticationTokenConfigurer customConfigurer() {
return new CustomJwtAuthenticationTokenConfigurer();
}
}
这假设 CustomJwtAuthenticationTokenFilter
扩展了 AbstractAuthenticationProcessingFilter
,但即使没有扩展,配置也会相似。