spring boot, Spring Security 认证 + JWT 最简完整代码 ----11
从「密码加密原理 → 手动认证 → JWT 生成 → 令牌校验」完整闭环,代码逐行带通俗注释,复制到
jobportal 项目可直接运行测试。一、先搞懂核心逻辑(大白话)
表格
| 课程阶段 | 作用 | 通俗比喻 |
|---|---|---|
| 密码哈希加密 | 数据库不存明文密码,存不可逆的哈希值 | 存指纹,不存原始密码,比对指纹验证 |
| 手动认证流程 | 代码主动调用 Security 校验账号密码 | 前台收身份证,交给后台核验真假 |
| JWT 令牌 | 登录成功后发一张「门禁卡」,后续请求带卡就行 | 不用每次输密码,刷卡进门 |
| JWT 过滤器 | 每个请求先检查「门禁卡」真假,有效就放行 | 门口保安,逐个查卡 |
二、第一步:新增依赖
在
pom.xml 里补充 JWT 工具库,其他依赖沿用之前的 Security、Web、JPAxml
<!-- JWT 生成与校验工具库 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
三、第二步:JWT 工具类(对应 125~128 集)
负责生成令牌、解析令牌、校验有效期,是整个 JWT 功能的核心
java
运行
package com.example.jobportal.security; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; @Component public class JwtUtil { // 密钥:学习用固定值,生产环境必须配置到环境变量,不能写死 private static final String SECRET = "my-jobportal-secret-key-must-be-long-enough-123456"; // 令牌有效期:1小时 = 3600000 毫秒 private static final long EXPIRE_TIME = 1000 * 60 * 60; /** * 生成密钥对象 */ private SecretKey getSignKey() { return Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); } /** * 1. 生成JWT令牌(登录成功后调用) * 对应课程127、128集 */ public String generateToken(String username) { return Jwts.builder() .subject(username) // 令牌主体:存用户名 .issuedAt(new Date()) // 签发时间 .expiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) // 过期时间 .signWith(getSignKey()) // 用密钥签名,防篡改 .compact(); } /** * 2. 从令牌中解析出用户名 */ public String extractUsername(String token) { return extractClaims(token).getSubject(); } /** * 3. 校验令牌是否有效 * 对应课程126集:签名校验 + 过期校验 */ public boolean validateToken(String token, String username) { String tokenUsername = extractUsername(token); return (username.equals(tokenUsername)) && !isTokenExpired(token); } /** * 判断令牌是否过期 */ private boolean isTokenExpired(String token) { return extractClaims(token).getExpiration().before(new Date()); } /** * 解析令牌所有内容(Payload) */ private Claims extractClaims(String token) { return Jwts.parser() .verifyWith(getSignKey()) .build() .parseSignedClaims(token) .getPayload(); } }
四、第三步:JWT 认证过滤器(对应 129~130 集)
每个请求进来先经过它:从请求头取 token → 校验 → 有效就把用户信息存入安全上下文
java
运行
package com.example.jobportal.security; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 1. 从请求头里取出令牌,规范格式:Authorization: Bearer xxxxxx String authHeader = request.getHeader("Authorization"); String token = null; String username = null; if (authHeader != null && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); // 去掉 "Bearer " 前缀 username = jwtUtil.extractUsername(token); // 解析出用户名 } // 2. 用户名不为空,且当前安全上下文没有认证信息(还没登录) if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 从用户服务里查出用户详情 UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 3. 校验令牌是否有效 if (jwtUtil.validateToken(token, userDetails.getUsername())) { // 4. 构建认证对象,存入安全上下文,告诉Spring Security:这个人已经登录了 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } // 5. 放行,继续执行后面的过滤器/接口 filterChain.doFilter(request, response); } }
五、第四步:完整安全配置
整合:内存用户、密码加密、权限规则、JWT 过滤器、跨域、关闭 CSRF
java
运行
package com.example.jobportal.config; import com.example.jobportal.security.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; /** * 核心安全过滤器链 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 关闭CSRF(前后端分离纯接口项目) .csrf(csrf -> csrf.disable()) // 开启跨域 .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 接口权限规则 .authorizeHttpRequests(auth -> auth // 登录接口、公开接口 所有人可访问 .requestMatchers("/api/auth/login", "/api/public/**").permitAll() // 管理员接口需要ADMIN角色 .requestMatchers("/api/admin/**").hasRole("ADMIN") // 其他所有接口必须登录 .anyRequest().authenticated() ) // 关键:关闭Session,用JWT令牌就不需要Session了 .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) // 把我们的JWT过滤器加到过滤器链最前面 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 关闭默认表单登录和弹窗登录 .formLogin(form -> form.disable()) .httpBasic(basic -> basic.disable()); return http.build(); } /** * 认证管理器(手动登录认证要用) * 对应课程123、124集:手动触发认证流程 */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } /** * 内存用户(对应121、122集) */ @Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.withUsername("zhangsan") .password(passwordEncoder.encode("123456")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(passwordEncoder.encode("admin123")) .roles("ADMIN", "USER") .build(); return new InMemoryUserDetailsManager(user, admin); } /** * 密码加密器(对应119、120集:BCrypt哈希加密) */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 跨域配置 */ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOriginPatterns(List.of("*")); config.setAllowedHeaders(List.of("*")); config.setAllowedMethods(List.of("*")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
六、第五步:登录接口(手动认证 + 发令牌)
对应课程 123、124、127、128 集:收账号密码 → 手动认证 → 成功返回 JWT
java
运行
package com.example.jobportal.controller; import com.example.jobportal.security.JwtUtil; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/auth") @RequiredArgsConstructor public class AuthController { private final AuthenticationManager authenticationManager; private final JwtUtil jwtUtil; /** * 登录接口 * 流程:接收账号密码 → 交给Security认证 → 成功生成JWT返回 */ @PostMapping("/login") public Map<String, Object> login(@RequestBody LoginRequest request) { // 1. 封装用户名密码,交给认证管理器校验(手动触发认证流程) Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getUsername(), request.getPassword() ) ); // 2. 认证成功,生成JWT令牌 String token = jwtUtil.generateToken(request.getUsername()); // 3. 返回结果 Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("message", "登录成功"); result.put("token", token); return result; } // 接收登录请求的内部类 @Data public static class LoginRequest { private String username; private String password; } }
七、第六步:测试接口
java
运行
package com.example.jobportal.controller; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class AuthTestController { // 需要登录才能访问 @GetMapping("/user/info") public String getUserInfo(Authentication authentication) { return "当前登录用户:" + authentication.getName() + ",角色:" + authentication.getAuthorities(); } // 需要管理员角色 @GetMapping("/admin/data") public String getAdminData() { return "管理员专属数据,只有ADMIN能看到"; } // 公开接口 @GetMapping("/public/hello") public String publicHello() { return "这是公开内容,任何人都能看"; } }
八、测试步骤(Postman 操作)
1. 登录获取令牌
- 请求:
POST http://localhost:8080/api/auth/login - 请求体(JSON):
json
{ "username": "zhangsan", "password": "123456" }
- 成功返回:
json
{ "code": 200, "message": "登录成功", "token": "eyJhbGciOiJIUzI1NiJ9.xxx.xxx" }
2. 携带令牌访问受保护接口
- 请求:
GET http://localhost:8080/api/user/info - 请求头添加:
plaintext
Authorization: Bearer 这里粘贴你拿到的token
- 预期:成功返回用户信息
- 不带 token 访问:返回 403 无权限
3. 测试管理员权限
用
admin / admin123 登录拿到 token,访问 /api/admin/data- 管理员账号:成功返回
- 普通用户账号:返回 403 无权限
九、课程知识点对应总结
表格
| 集数 | 核心内容 | 对应代码 |
|---|---|---|
| 119 | 编码 / 加密 / 哈希 三者区别 | BCrypt 属于哈希,不可逆,存密码用 |
| 120 | 密码编码器 PasswordEncoder | PasswordEncoder Bean |
| 121、122 | 内存用户管理器 | InMemoryUserDetailsManager |
| 123、124 | 手动触发认证流程 | AuthenticationManager.authenticate() |
| 125、126 | JWT 原理、签名校验机制 | JwtUtil 生成 + 校验逻辑 |
| 127、128 | 后端生成 JWT 令牌 | generateToken() 方法 |
| 129、130 | JWT 过滤器、校验令牌、存入上下文 | JwtAuthenticationFilter |
💡 关键理解:传统 Session 登录是「服务端存用户状态」,JWT 是「把用户信息加密后存在客户端令牌里」,服务端只需要校验签名真假,不用存 Session,更适合分布式、前后端分离项目。

浙公网安备 33010602011771号