spring security结合jwt实现用户重复登录处理

实现用户重复登录处理的一种常用方法是结合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技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • java简单快速排序实例解析

    Java简单快速排序实例解析 快速排序是一种常用的排序算法,其本质是通过不断地把数列分成两个部分,分别进行递归排序,最终完成整个数列的排序。 实现思路 快速排序的实现思路如下: 选择一个基准元素,在数列中选择一个数作为基准元素pivot,一般选择第一个或者最后一个元素; 分割数组,将数列中所有小于基准元素的数放在它的左侧,所有大于基准元素的数放在它的右侧; …

    Java 2023年5月19日
    00
  • 教你用Java SpringBoot如何解决跨域

    教你用 Java Spring Boot 如何解决跨域 在本文中,我们将详细讲解如何使用 Java Spring Boot 解决跨域问题。我们将使用 Spring Boot 2.5.0 版本的源码进行分析。 什么是跨域? 跨域是指在浏览器中,当一个 Web 应用程序试图访问另一个域名下的资源时,浏览器会阻止这种行为。这是由于浏览器的同源策略所导致的。同源策略…

    Java 2023年5月15日
    00
  • MyBatis批量插入的五种方式小结(MyBatis以集合方式批量新增)

    MyBatis批量插入的五种方式小结 在使用MyBatis进行批量插入时,有多种方式可以选择。本文将介绍MyBatis批量插入的五种方式,并提供示例代码,以便读者更好地理解这些方法。 方式一:使用for循环单条插入 在使用for循环单条插入时,需要在for循环中执行insert语句。这种方式的优点是插入的数据可以轻松地进行转换,缺点是插入效率较低。 priv…

    Java 2023年6月1日
    00
  • Java使用JDBC连接数据库的详细步骤

    下面就为你详细讲解一下“Java使用JDBC连接数据库的详细步骤”的完整攻略。 前置要求 在使用JDBC连接数据库前,我们需要有以下前置要求: 下载相应数据库的JDBC驱动程序 数据库的连接信息,比如:数据库名称,用户名和密码等 步骤一:导入JDBC驱动程序 先导入所下载的JDBC驱动程序,可以使用以下代码: try { // 加载MySQL的JDBC驱动 …

    Java 2023年5月19日
    00
  • 微信小程序登录态和检验注册过没的app.js写法

    微信小程序登录态和检验注册的实现涉及到小程序端的代码和服务端的代码,因此在您进行开发时需要分别处理。 实现登录态 小程序的登录态是通过wx.login()获取的,具体实现步骤如下: 在小程序中,在需要登录的页面中,首先调用wx.login()获取到微信返回的code码,然后使用wx.request()将该code码发送到服务端。以下是示例代码: wx.log…

    Java 2023年5月23日
    00
  • SpringBoot+Mybatis实现登录注册的示例代码

    以下是详细的攻略: Step 1:环境搭建 首先需要安装JDK、Maven以及Spring Boot Step 2:新建Spring Boot项目 新建一个Spring Boot项目,选择Maven项目类型。在pom.xml文件中添加Mybatis和MySQL驱动的依赖即可。 Step 3:配置数据库 在application.properties文件中配置…

    Java 2023年5月20日
    00
  • springboot+jwt+springSecurity微信小程序授权登录问题

    背景介绍 在微信小程序中实现授权登录是一个常见的需求,一般情况下我们可以通过使用微信提供的API实现相关功能。然而,在某些需要更加丰富的业务场景下,如需要融合第三方登录、访问权限控制以及身份验证等功能时,就需要我们使用 SpringBoot+JWT+Spring Security 来实现这些需求。 Spring Security 采用基于过滤器链的结构,通过…

    Java 2023年5月20日
    00
  • java 中断线程的几种方式 interrupt()详解

    Java 中断线程的几种方式 interrupt()详解 在 Java 中,一条线程可以通过另一条线程中断,可以说是线程通信的一种方式。本文将会详细的讲解 Java 中线程中断的几种方式以及如何检测线程是否被中断。 interrupt() 方法 Java 提供了 interrupt() 方法作为一种中断线程的方式,在线程启动后,可以使用该方法将线程设置为中断…

    Java 2023年5月18日
    00
合作推广
合作推广
分享本页
返回顶部