我的要求是限制多个用户登录,并在应用程序中一次只允许一个用户登录。 我的应用程序是一个 Spring Boot 应用程序,为用户授权和身份验证实现了 JWT 身份验证。 我读了几篇文章,了解到可以在 Spring Boot 中使用 Spring Security 来实现。
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();
}
}
这是我的配置类,我在其中添加了所需的代码。 还添加了一个新类,如下所示:
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 你能检查一下我现在做错了什么吗?
最佳答案
首先,让我们考虑一下在使用表单登录的应用程序中,在没有自定义过滤器的简单情况下并发 session 控制如何工作。
有效的登录请求将到达UsernamePasswordAuthenticationFilter
。
在此过滤器中,通过调用 SessionAuthenticationStrategy#onAuthentication
检查 session 并发限制。
如果未超过最大限制,则用户登录成功。如果超出限制,则会抛出SessionAuthenticationException
,并向用户返回错误响应。
要在自定义过滤器中具有相同的行为,您需要确保在 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-boot - 无法使用自定义 Filter 在 Spring Security 中实现 session 限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65182973/