实现用户重复登录处理的一种常用方法是结合Spring Security和JWT的认证机制。下面是实现该方法的详细攻略,包括两个示例。
准备工作
首先,需要在Spring Boot项目中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.2</version>
</dependency>
同时,在Spring Security的配置文件中添加以下配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().anyRequest().authenticated().and()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
return new JwtAuthenticationTokenFilter();
}
}
其中,JwtAuthenticationTokenFilter
是自定义的过滤器,用于处理JWT认证的逻辑。
示例1:禁止重复登录
在默认情况下,Spring Security的身份验证机制允许同一用户重复登录。如果想禁止重复登录,则需要实现一个自定义的UserDetails
,并在登录时将UserDetails
保存到HttpSession
中。当用户尝试进行第二次登录时,系统应该检查当前用户是否已经登录了。如果已经登录,则拒绝登录请求。
自定义UserDetails
如下:
public class WebUserDetails implements UserDetails {
private List<GrantedAuthority> authorities;
private String username;
private String password;
private boolean enabled;
private String sessionId; // 新增字段:用于保存sessionId
// 省略构造方法和Getter/Setter
}
在登录时,需要将WebUserDetails
保存到HttpSession
中,代码如下:
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginForm form, HttpServletRequest request) {
String username = form.getUsername();
String password = form.getPassword();
// 进行身份验证,并获取用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("密码错误");
}
// 将用户信息保存到HttpSession中
((WebUserDetails) userDetails).setSessionId(request.getSession().getId());
request.getSession().setAttribute("user", userDetails);
// 生成并返回JWT令牌
String token = jwtUtil.generateToken(userDetails.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
}
自定义的JwtAuthenticationTokenFilter
会在请求到达时检查JWT身份验证,并判断当前用户是否已经登录,如果已经登录则将请求拒绝。
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authToken = jwtUtil.getTokenFromHttpRequest(request);
String username = jwtUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 检查当前用户是否已经登录
String sessionId = ((WebUserDetails) userDetails).getSessionId();
if (sessionId.equals(request.getSession().getId())) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
示例2:强制下线已登录的用户
在某些情况下,可能需要强制下线已登录的用户。这种情况下,每次登录时要检测是否有另一个用户正在使用同一个账户进行登录。如果有,则将这个用户的会话无效化,使其下线。
具体实现:在登录时,判断当前账户是否已经在其它机器上登录;如果已经登录,则清除该账户的历史登录信息,并允许当前登录操作。
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginForm form, HttpServletRequest request) {
String username = form.getUsername();
String password = form.getPassword();
// 进行身份验证,并获取用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("密码错误");
}
// 判断当前账户是否已经在其它机器上登录
String sessionId = ((WebUserDetails) userDetails).getSessionId();
HttpSession session = request.getSession(false);
if (session != null) {
WebUserDetails oldUser = (WebUserDetails) session.getAttribute("user");
if (oldUser != null && !oldUser.getSessionId().equals(sessionId)) {
session.invalidate();
}
}
// 将用户信息保存到HttpSession中
((WebUserDetails) userDetails).setSessionId(request.getSession().getId());
request.getSession().setAttribute("user", userDetails);
// 生成并返回JWT令牌
String token = jwtUtil.generateToken(userDetails.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
}
注意:为了能够获取不同机器上的用户身份信息,需要在WebUserDetails
中增加一个额外的字段,保存最后登录时间或最后登录的IP地址等信息。
以上是结合Spring Security和JWT实现用户重复登录处理的完整攻略,包括两个示例。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring security结合jwt实现用户重复登录处理 - Python技术站