Spring Boot 3.3.7整合Spring Security+JWT认证实战教程

Spring Boot 3.3.7整合Spring Security+JWT认证实战教程

在现代Web开发中,身份认证和授权是保障系统安全的关键环节。Spring Security作为Spring生态中的安全框架,提供了强大的安全功能,而JWT(JSON Web Token)则是一种轻量级的无状态认证机制。本文将详细介绍如何在Spring Boot 3.3.7项目中整合Spring Security和JWT,实现安全的认证与授权功能。

一、项目初始化

1. 创建Spring Boot项目

  1. 创建一个Spring Boot项目。
  2. 选择以下依赖项:
    • Spring Web
    • Spring Security
    • Lombok
    • commons-lang3
    • jjwt-jackson
    • jjwt-impl
    • jjwt-api

2. 引入依赖

pom.xml中添加以下依赖:

<dependencies>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
</dependencies>

二、JWT工具类

1. 创建JwtUtil

JwtUtil类用于生成和解析JWT Token。以下是关键代码:

private static final String SECRET_KEY = "1234567890123456789012345678901234567890";

    public static String getUsername(String token) {
        try{
            String username = extractClaim(token, Claims::getSubject);
            return username;
        }catch (Exception e){
            e.printStackTrace();
            return "";
        }
    }

    public static Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public static <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    public static Claims extractAllClaims(String token) {

        byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);
        return Jwts.parserBuilder()
                .setSigningKey(key) // 使用 SecretKey 进行签名验证
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private static Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public static String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

   
    public static String createToken(Map<String, Object> claims, String subject) {

        byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 )) // 设置过期时间为1小时后
                .signWith(key, SignatureAlgorithm.HS256) // 使用 SecretKey 和指定的签名算法
                .compact();
    }

三、自定义用户详情服务

1. 创建CustomUserDetailsService

CustomUserDetailsService类实现了UserDetailsService接口,用于加载用户信息。以下是关键代码:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 示例:硬编码用户(实际应从数据库读取)
        if ("admin".equals(username)) {
            // 构建并返回一个 UserDetails 对象,包含用户名、加密后的密码和角色信息
            return User.builder()
                    .username("admin")
                    .password(passwordEncoder.encode("123456"))
                    .roles("ADMIN")
                    .build();
        }
        // 如果用户名不存在,抛出 UsernameNotFoundException 异常
        throw new UsernameNotFoundException("用户不存在");
    }
}

四、JWT过滤器

1. 创建JwtFilter

JwtFilter类继承自OncePerRequestFilter,用于拦截请求并验证JWT Token。以下是关键代码:

public class JwtFilter extends OncePerRequestFilter {

    private static final String TOKEN_HEADER = "token";

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain)
            throws ServletException, IOException {
        // 1. 获取请求头中的Token
        String requestToken = request.getHeader(TOKEN_HEADER);
        String requestURI = request.getRequestURI();
        if ("/login".equals(requestURI)){
            chain.doFilter(request, response);
            return;
        }

        if(StringUtils.isBlank(requestToken)){
            // 如果Token为空,返回401未授权状态码和错误信息
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(401);
            response.getWriter().write("""
            {
                "code": 401,
                "msg": "token为空",
                "timestamp": %d
            }
            """.formatted( System.currentTimeMillis()));
            return;
        }

        // 从Token中解析出用户名
        String username = JwtUtil.getUsername(requestToken);
        if(StringUtils.isNoneBlank(username)){
            List<String> roles = List.of("ADMIN","USER");
            List<SimpleGrantedAuthority> list = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList();
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,null,list);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            chain.doFilter(request, response);
        }else {
            // 如果Token无效,返回401未授权状态码和错误信息
            log.info("token错误");
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(401);
            response.getWriter().write("""
            {
                "code": 401,
                "msg": "token错误",
                "timestamp": %d
            }
            """.formatted( System.currentTimeMillis()));
        }
    }
}

五、安全配置

1. 创建SecurityConfig

SecurityConfig类用于配置Spring Security的安全规则。以下是关键代码:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

   
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 禁用CSRF保护
                .csrf(AbstractHttpConfigurer::disable)
                // 配置请求的授权规则
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/get").hasRole("ADMIN") // 只有ADMIN角色可以访问/get路径
                        .requestMatchers("/login").permitAll() // 允许所有用户访问/login路径
                        .anyRequest().authenticated() // 其他所有请求都需要认证
                )
                // 在认证流程前添加JwtFilter过滤器
                .addFilterBefore(
                        new JwtFilter(),
                        UsernamePasswordAuthenticationFilter.class
                )
                // 禁用表单登录
                .formLogin(AbstractHttpConfigurer::disable)
                // 禁用会话管理
                .sessionManagement(SessionManagementConfigurer::disable)
                // 配置异常处理
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())  // 处理认证异常
                        .accessDeniedHandler(new CustomAccessDeniedHandler())  // 处理授权异常
                );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

六、认证入口与异常处理

1. 创建CustomAuthenticationEntryPoint

CustomAuthenticationEntryPoint类用于处理认证失败的逻辑。以下是关键代码:

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException ex)
            throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(401);
        response.getWriter().write("""
            {
                "code": 401,
                "msg": "身份认证失败: %s",
                "timestamp": %d
            }
            """.formatted(ex.getMessage(), System.currentTimeMillis()));
    }
}

2. 创建CustomAccessDeniedHandler

CustomAccessDeniedHandler类用于处理权限不足的逻辑。以下是关键代码:

public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException ex)
            throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(403);
        response.getWriter().write("""
                {
                    "code": 403,
                    "msg": "权限不足: %s",
                    "requiredRole": "ADMIN", // 示例动态字段
                    "timestamp": %d
                }
                """.formatted(ex.getMessage(), System.currentTimeMillis()));
    }

七、登录控制器

1. 创建LoginController

LoginController类提供登录接口,生成JWT Token。以下是关键代码:

@RestController
public class LoginController {
    private final AuthenticationManager authenticationManager;

    public LoginController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest request) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            request.getUsername(),
                            request.getPassword()
                    )
            );
            SecurityContextHolder.getContext().setAuthentication(authentication);
            String token = JwtUtil.generateToken(request.getUsername());
            return ResponseEntity.ok("登录成功:"+token);
        } catch (Exception e) {
            return ResponseEntity.status(401).body("认证失败: " + e.getMessage());
        }
    }
    @GetMapping("get")
    public String get(){
        return "success";
    }

    // 登录请求参数类
    @Data
    public static class LoginRequest {
        private String username;
        private String password;
    }
}

八、运行与测试

1. 启动项目

  1. 启动SkyApplication,访问http://localhost:8080/login进行登录测试。
  2. 示例请求体:
    {
        "username": "admin",
        "password": "123456"
    }
    
  3. 登录成功后,返回JWT Token。

2. 访问受保护资源

  1. 示例:访问http://localhost:8080/get,需要在请求头中添加token字段。

九、总结

本文详细介绍了如何在Spring Boot 3.3.7中整合Spring Security和JWT,实现了安全的认证与授权功能。通过自定义用户详情服务、JWT过滤器和安全配置,我们构建了一个灵活且安全的认证系统。希望本文对你有所帮助!

源码地址:

https://download.youkuaiyun.com/download/Crazy7426/90448031
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值