目录
[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的头部和载荷。
-
服务器使用选定的算法(如
HS256或RS256)和对应的密钥(对称密钥或非对称私钥)对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("用户名或密码错误");
}
}
}