使用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("用户名或密码错误");
        }
    }
}
相关推荐
用户962377954484 小时前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机7 小时前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机7 小时前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954488 小时前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star9 小时前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户9623779544812 小时前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher2 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行5 天前
网络安全总结
安全·web安全
red1giant_star5 天前
手把手教你用Vulhub复现ecshop collection_list-sqli漏洞(附完整POC)
安全
ZeroNews内网穿透5 天前
谷歌封杀OpenClaw背后:本地部署或是出路
运维·服务器·数据库·安全