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数组的使用 什么是数组? 数组是一种存储数据的集合。它可以存储多个同类型的数据元素,并且这些元素按照一定顺序排列。Java数组是一种容器,可以存储固定大小的同类型元素。数组可以存储基本数据类型和对象,但必须是同一个类型。 声明和初始化数组 声明一个数组需要指定数据类型和数组名称。 //声明一个整型数组 int[] arr; 初始化数组需要…

    Java 2023年5月26日
    00
  • 八、设置HTTP应答头

    设置HTTP应答头是Web开发中非常重要的一个过程,通过设置HTTP应答头,可以有效地控制浏览器的行为以及网站的运作。在本篇攻略中,我们将详细讲解设置HTTP应答头的步骤。 1.什么是HTTP应答头 HTTP应答头是HTTP请求中的一部分,用于告诉浏览器如何处理服务器发送的数据。HTTP应答头包含了很多有用的信息,例如文件类型、编码方式、缓存策略等。 2.设…

    Java 2023年6月16日
    00
  • 研究桃源留言本的漏洞

    研究桃源留言本的漏洞攻略: 一、介绍桃源留言本 桃源留言本是一个用PHP编写的简单留言板程序,原作者为huhuweb。该程序具有易用性、易修改的特点,因此可以广泛应用于小型网站的留言功能。不过,由于其代码较为简单,程序存在多处安全漏洞,需要加强安全设置。 二、审计桃源留言本 针对桃源留言本的漏洞进行审计,可抓取请求包,利用工具进行源代码分析、SQL注入等一系…

    Java 2023年6月16日
    00
  • java编程题之从上往下打印出二叉树

    Java编程题之从上往下打印出二叉树 题目描述 给定一棵二叉树的根节点,从上往下按层打印出这个二叉树,同一层的节点按照从左到右的顺序打印。 例如,给定一个如下所示的二叉树: 8 / \ 6 10 / \ / \ 5 7 9 11 打印出的顺序为:8 6 10 5 7 9 11。 解题思路 此题的解法可以用到二叉树的遍历,我们可以用队列来保存每一层的节点。 将…

    Java 2023年5月26日
    00
  • Java如何实现支付宝电脑支付基于servlet版本

    Java 如何实现支付宝电脑支付基于 Servlet 版本,具体的实现步骤如下: 1. 注册支付宝商家账号 首先需要注册一个支付宝商家账号。 2. 下载支付宝开发者工具包 下载支付宝提供的开发者工具包,官方推荐使用 Java 版本的 SDK。 3. 创建订单 在进行支付前需要创建一个订单,在创建订单时需要填写订单的一些基本信息,例如订单金额、商品名称、订单号…

    Java 2023年5月26日
    00
  • 详解如何探测小程序返回到webview页面

    探测小程序返回到webview页面主要有两个部分:小程序侧的操作和webview侧的操作。 小程序侧的操作 步骤一:调用小程序JSAPI 小程序提供了navigateBackMiniProgram的JSAPI,可以在小程序内部调用,从而返回webview页面。 wx.navigateBackMiniProgram({ success: function() …

    Java 2023年5月23日
    00
  • springboot+gradle 构建多模块项目的步骤

    下面是详细讲解“springboot+gradle 构建多模块项目的步骤”的完整攻略。 四步构建多模块项目 第一步:创建父项目 在开始构建多模块项目之前,我们需要先创建一个父项目,用于管理多个子模块的依赖关系。使用gradle构建的项目通常有一个根目录,这个根目录下通常会有一个build.gradle文件,当然也可以包含其他文件和目录,具体的结构可以按照实际…

    Java 2023年5月31日
    00
  • java Scanner输入数字、字符串过程解析

    接下来我将为您提供关于Java中Scanner输入数字、字符串的详细描述。 Scanner类 Java中的Scanner类提供了一种可以解析基本数据类型和字符串的简便方法。Scanner可以从文件、输入流、文本字符串和其他源读取格式化的输入内容。我们可以使用Scanner进行数字和字符串输入处理。 以下是Scanner类的构造方法: Scanner(Inpu…

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