使用springsecurity+mybatis+mysql是如何实现JWT登陆

目录

1、流程图

2、后端实现JWT登陆

[2.1 环境准备与依赖配置](#2.1 环境准备与依赖配置)

[2.2 数据库表设计](#2.2 数据库表设计)

[2.3 实现核心组件](#2.3 实现核心组件)


1、流程图

关键步骤:

步骤 核心组件 说明
1. 用户登录 自定义登录过滤器 或 认证控制器 客户端提交用户名/密码,服务端验证并生成JWT返回
2. JWT生成 JWT工具类 (JwtTokenUtil) 登录成功时,根据用户信息(如用户名、权限)生成accessToken和refreshToken
3. 令牌校验 JWT认证过滤器 (JwtAuthenticationFilter) 对后续请求,从Header提取JWT,进行验签、过期检查,并加载用户权限
4. 权限鉴定 Spring Security + UserDetailsService 通过UserDetailsService从MySQL加载用户详情及权限,供Spring Security进行授权决策
5. 令牌刷新 刷新令牌接口 当accessToken过期,客户端使用refreshToken获取新的accessToken

2、后端实现JWT登陆

2.1 环境准备与依赖配置

pom.xml 中引入必要的依赖

XML 复制代码
<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MyBatis 集成 Spring Boot -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>你的版本</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>你的版本</version>
</dependency>
<!-- JWT 库,例如 jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>你的版本</version>
</dependency>

application.yml 中配置数据库连接等信息

bash 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_database?serverTimezone=UTC
    username: your_username
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver
# 可选的JWT自定义配置
jwt:
  secret: your-secret-key # 用于签名JWT的密钥,务必保密
  expiration: 86400000 # Token有效期(毫秒),例如24小时

**JWT密钥:**可以使用对称密钥或非对称密钥。JWT使用主要涉及两个场景:创建(签名)和验证

  • 创建/签名时

    • 用户登录,服务端验证其凭据(如用户名密码)通过后。

    • 服务器生成JWT的头部和载荷。

    • 服务器使用选定的算法(如 HS256RS256)和对应的密钥(对称密钥或非对称私钥)对JWT进行签名

    • 将签名附加到JWT的第三部分,形成一个完整的JWT,然后发送给客户端。

  • 验证时

    • 客户端在后续请求的 Authorization 头中携带JWT。

    • 服务器(或API网关)收到JWT。

    • 服务器首先验证JWT的格式和过期时间。

    • 服务器使用与签名时对应的算法和密钥来验证签名

      • 服务器会重新计算签名,并与JWT中附带的签名进行比对。如果一致,证明令牌未被篡改且来源可信。

适用场景:

  • 对称密钥:单一应用内部,或者所有微服务都在一个完全可信的内部网络中,并且可以安全地共享同一个密钥。
  • 非对称密钥:现代分布式系统和微服务架构的推荐做法 。例如:
    • 一个独立的认证授权服务器用私钥签发JWT。

    • 多个资源服务器API服务器使用公开发布的公钥来验证JWT。

2.2 数据库表设计

实现基于角色的访问控制(RBAC)模型,通常至少需要以下五张核心表:

  • 用户表 (sys_user): 存储用户名、密码(加密后)等核心信息。

  • 角色表 (sys_role): 定义系统角色,如 ADMIN, USER。

  • 权限表 (sys_permission): 定义具体的权限点,如 user:read, order:write。

  • 用户角色关联表 (sys_user_role): 建立用户与角色的多对多关系。

  • 角色权限关联表 (sys_role_permission): 建立角色与权限的多对多关系。

2.3 实现核心组件

**创建JWT工具类:**这个类负责Token的生成、解析和验证。

java 复制代码
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtTokenUtil {
    // 从配置中读取,例如 @Value("${jwt.secret}")
    private String secret = "your-secret-key"; 
    private Long expiration = 86400000L; 

    // 生成JWT Token
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("sub", userDetails.getUsername()); // 主题设为用户名
        claims.put("created", new Date());
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    // 从Token中解析出用户名
    public String getUsernameFromToken(String token) {
        return getClaimsFromToken(token).getSubject();
    }

    // 验证Token是否有效
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    // 其他辅助方法:解析Token体、判断Token是否过期等...
    private Claims getClaimsFromToken(String token) { ... }
    private Boolean isTokenExpired(String token) { ... }
}

**实现UserDetailsService:**这个接口是Spring Security加载用户核心信息(用户名、密码、权限)的桥梁。

java 复制代码
@Service
public class JwtUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper; // 你的MyBatis Mapper

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 通过MyBatis从数据库查询用户信息(包含密码)
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }

        // 2. 查询该用户的权限列表(例如:ROLE_ADMIN, user:read)
        List<GrantedAuthority> authorities = new ArrayList<>();
        // 假设 getUserPermissions 方法通过MyBatis查询权限字符串集合
        for (String permission : getUserPermissions(user.getId())) {
            authorities.add(new SimpleGrantedAuthority(permission));
        }

        // 3. 返回Spring Security需要的UserDetails对象
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(), // 这里应该是加密后的密码
                authorities);
    }
}

**配置Spring Security:**这是最关键的配置类,用于串联整个安全流程。

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtUserDetailsService userDetailsService;
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; // 认证失败处理器
    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler; // 权限不足处理器
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 推荐使用BCrypt强哈希加密密码
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // 关闭CSRF和CORS
                .csrf().disable()
                .cors().disable()
                
                // 设置异常处理器
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .and()
                
                // 设置会话管理为无状态
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                
                // 配置请求授权规则
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers("/api/auth/login").permitAll() // 登录接口放行
                        .requestMatchers("/admin/**").hasRole("ADMIN") // 管理员路径
                        .anyRequest().authenticated() // 其他所有请求都需要认证
                )
                
                // 添加我们自定义的JWT Token过滤器
                .addFilterBefore(new JwtAuthenticationTokenFilter(jwtTokenUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

**创建JWT认证过滤器:**这个过滤器负责在每次请求时校验JWT Token。

java 复制代码
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private JwtTokenUtil jwtTokenUtil;
    private UserDetailsService userDetailsService;

    // 从请求头中获取Token的字段名
    private String tokenHeader = "Authorization";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 1. 从请求头获取Token
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String authToken = authHeader.substring(7); // 去掉"Bearer "前缀
            
            // 2. 从Token中解析出用户名
            String username = jwtTokenUtil.getUsernameFromToken(authToken);
            
            // 3. 如果用户名不为空,且当前Security上下文尚无认证信息
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                
                // 4. 加载用户信息
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                
                // 5. 验证Token是否有效
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    // 6. 创建认证令牌,并设置到Security上下文中,表示用户已认证
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

实现登录接口(获取Token):最后开放一个接口来接收登录请求并返回JWT Token。

java 复制代码
@RestController
public class JwtAuthController {

    @Autowired
    private AuthenticationManager authenticationManager; // 需要在上面的Config中@Bean注入
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private JwtUserDetailsService userDetailsService;

    @PostMapping("/api/auth/login")
    public ResponseEntity<?> login(@RequestBody @Valid LoginRequest loginRequest) {
        try {
            // 1. 使用Spring Security的AuthenticationManager进行认证
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            loginRequest.getUsername(),
                            loginRequest.getPassword()
                    )
            );
            
            // 2. 认证成功,将认证信息设置到上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);
            
            // 3. 重新加载UserDetails(也可以直接从authentication.getPrincipal()获取)
            UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
            
            // 4. 生成JWT Token
            String token = jwtTokenUtil.generateToken(userDetails);
            
            // 5. 返回Token
            return ResponseEntity.ok(new JwtResponse(token));
            
        } catch (BadCredentialsException e) {
            // 用户名或密码错误
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码错误");
        }
    }
}
相关推荐
TH88863 小时前
水陆安全监测仪:水域陆域的“安全哨兵”
安全
llxxyy卢4 小时前
SQL注入之SQLMAP绕过WAF(安全狗)
服务器·安全
红树林075 小时前
渗透测试之json_web_token(JWT)
网络协议·安全·web安全
云动雨颤5 小时前
网站被劫持后怎么恢复?
安全·dns
m0_738120727 小时前
内网横向靶场——记录一次横向渗透(三)
开发语言·网络·安全·web安全·网络安全·php
codervibe9 小时前
协议欺骗工程实践:HTTP/FTP/Telnet/SSH 的伪装与实现要点
安全
软件供应链安全指南9 小时前
“基于‘多模态SCA+全周期协同’的中间件开源风险治理实践”荣获OSCAR开源+安全及风险治理案例
安全·中间件·开源
KKKlucifer10 小时前
技术漏洞被钻营!Agent 感知伪装借 ChatGPT Atlas 批量输出虚假数据,AI 安全防线面临新挑战
人工智能·安全·chatgpt