spring boot, Spring Security 认证 + JWT 最简完整代码 ----11

 

从「密码加密原理 → 手动认证 → JWT 生成 → 令牌校验」完整闭环,代码逐行带通俗注释,复制到 jobportal 项目可直接运行测试。

一、先搞懂核心逻辑(大白话)

表格
 
课程阶段作用通俗比喻
密码哈希加密 数据库不存明文密码,存不可逆的哈希值 存指纹,不存原始密码,比对指纹验证
手动认证流程 代码主动调用 Security 校验账号密码 前台收身份证,交给后台核验真假
JWT 令牌 登录成功后发一张「门禁卡」,后续请求带卡就行 不用每次输密码,刷卡进门
JWT 过滤器 每个请求先检查「门禁卡」真假,有效就放行 门口保安,逐个查卡

二、第一步:新增依赖

pom.xml 里补充 JWT 工具库,其他依赖沿用之前的 Security、Web、JPA
xml
 
 
<!-- 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,更适合分布式、前后端分离项目。
 
 
posted @ 2026-08-01 01:26  漫漫长路</>  阅读(10)  评论(0)    收藏  举报