侧边栏壁纸
博主头像
Hello石狐

平凡的日子里,也要习惯记录

  • 累计撰写 18 篇文章
  • 累计创建 4 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

创建一个SpringSecurity+JWT的认证和授权应用

石狐
2025-08-02 / 0 评论 / 0 点赞 / 19 阅读 / 0 字

相关依赖

首先需要创建一个基础SpringBoot工程项目

    代码片段...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.3</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Security和JWT依赖单独列在这里

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- 引入3个JWT依赖jjwt-api,jjwt-impl,jjwt-jackson -->
        <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>

配置JWT

写一个jwt工具类,用于生成token,验证等操作

package com.chens.common.security;

import io.jsonwebtoken.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Date;

/**
 * JWT工具类 jwtTokenUtils
 * @description 用于生成和验证token令牌
 * @author chensh
 * @date 2025/08/01 16:22
 */
@Component
public class JwtTokenUtils {

    @Value("${app.jwt-secret}")
    private String jwtSecret;

    @Value("${app.jwt-expiration-ms}")
    private int jwtExpirationMs;

    public String generateToken(Authentication authentication) {
        UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationMs);

        return Jwts.builder()
                .setSubject(userPrincipal.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUsernameFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            // Invalid JWT signature
        } catch (MalformedJwtException ex) {
            // Invalid JWT token
        } catch (ExpiredJwtException ex) {
            // Expired JWT token
        } catch (UnsupportedJwtException ex) {
            // Unsupported JWT token
        } catch (IllegalArgumentException ex) {
            // JWT claims string is empty
        }
        return false;
    }

    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

有两个参数需要配置,到配置文件中配置,配置完再进行下一步

`jwtSecret`: token生成的密钥(需要自己生成)

`jwtExpirationMs`: token有效事件

jwtSecret生成工具类:

/**
 * 生成JWT密钥
 */
public class KeyGeneratorUtil {
    public static void main(String[] args) {
        SecureRandom secureRandom = new SecureRandom();
        byte[] key = new byte[256]; // 256位密钥
        secureRandom.nextBytes(key);
        String base64Key = Base64.getEncoder().encodeToString(key);
        System.out.println("JWT Secret: " + base64Key);
    }
}

配置Security

需要配置核心的五个类:

  • 过滤器:用于对请内的token进行解析,创建认证对象,交给Security上下文处理

  • 未授权请求处理器:处理没有权限的请求,并自定义响应体

  • 认证入口类:处理未认证/没有token请求/token失效,自定义响应体

  • 自定义用户查询类:用于查询数据库的用户,提供给Security上下文

  • SpringSecurity配置类:用于配置SpringSecurity的安全策略、认证方式、授权方式等

1、过滤器 JwtAuthenticationFilter

package com.chens.common.filter;

import com.chens.common.exception.UnauthorizedException;
import com.chens.common.security.JwtTokenUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
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.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * JWT认证过滤器
 * @description 用于所有请求进来之前,从请求头中解析JWT令牌并进行认证
 * @author chensh
 * @date 2025/08/01 16:22
 */
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenUtils jwtTokenUtils;

    private final UserDetailsService userDetailsService;

    @Autowired
    public JwtAuthenticationFilter(JwtTokenUtils jwtTokenUtils, UserDetailsService userDetailsService) {
        this.jwtTokenUtils = jwtTokenUtils;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 从 request 获取 JWT token
        String jwt = jwtTokenUtils.getJwtFromRequest(request);

        if (StringUtils.hasText(jwt)) {
            // 验证 JWT token
            if (!jwtTokenUtils.validateToken(jwt)) {
                throw new UnauthorizedException("Invalid or expired JWT token");
            }

            // 从 JWT token 中获取用户名
            String username = jwtTokenUtils.getUsernameFromJWT(jwt);
            // 从数据库中查询用户信息
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            // 创建认证对象
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities());
            // 设置认证详情
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            // 将认证对象设置到 SecurityContextHolder 中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }
}

2、未授权请求处理器 CustomAccessDeniedHandler

package com.chens.common.security;

import com.chens.payload.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import java.io.IOException;

/**
 * 自定义访问拒绝处理器
 * @description 用于处理未授权请求(接口没有权限),返回403禁止访问状态码和JSON格式的错误信息
 * @author chenshun
 * @date 2023/08/10 16:22
 */
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        ApiResponse<?> apiResponse = ApiResponse.error(HttpStatus.FORBIDDEN.value(), "未授权");

        response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
    }
}

3、认证入口类 CustomAuthenticationEntryPoint

package com.chens.common.security;

import com.chens.payload.ApiResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;

/**
 * 自定义认证入口点
 * @description 用于处理未认证请求(未登录/token失效),返回401未授权状态码和JSON格式的错误信息(
 * @author chensh
 * @date 2025/08/01 16:22
 */
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        
        ApiResponse<?> apiResponse = ApiResponse.error(HttpStatus.UNAUTHORIZED.value(), "认证失败:" + authException.getMessage());
        response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
    }
}

4、自定义用户查询类 CustomUserDetailsService

package com.chens.common.security;

import com.chens.modules.system.model.User;
import com.chens.modules.system.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;

/**
 * 自定义用户详情服务
 * @description 用于根据用户名或邮箱加载用户详情信息,并返回UserDetails对象,提供给SpringSecurity上下文使用
 * @author chenshun
 * @date 2023/08/10 16:22
 */
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(usernameOrEmail)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail));

        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(),
                Collections.singletonList(new SimpleGrantedAuthority(user.getRole())));
    }
}

5、SpringSecurity配置类 SpringSecurityConfig

package com.chens.common.security;

import com.chens.common.filter.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * SpringSecurity配置类
 * @description 用于配置SpringSecurity的安全策略、认证方式、授权方式等
 * @author chensh
 * @date 2025/08/01 16:22
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SpringSecurityConfig {

    /**
     * 自定义用户详情服务
     */
    private final CustomUserDetailsService userDetailsService;
    /**
     * JWT工具类
     */
    private final JwtTokenUtils jwtTokenUtils;
    /**
     * 自定义认证失败处理器
     */
    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    /**
     * 自定义授权失败处理器
     */
    private final CustomAccessDeniedHandler customAccessDeniedHandler;

    @Autowired
    public SpringSecurityConfig(CustomUserDetailsService userDetailsService, JwtTokenUtils jwtTokenUtils, CustomAuthenticationEntryPoint customAuthenticationEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtils = jwtTokenUtils;
        this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
        this.customAccessDeniedHandler = customAccessDeniedHandler;
    }

    /**
     * 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    /**
     * 认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }


    /**
     * Security 配置安全过滤链
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 由于使用的是JWT,我们这里不需要csrf
        http.csrf(csrf -> csrf.disable());

        // 禁用session
        http.sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        // 添加自定义未授权和未登录结果返回
        http.exceptionHandling(customizer  -> customizer
                // 处理未登录
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                // 处理未授权
                .accessDeniedHandler(customAccessDeniedHandler));

        // 配置请求授权
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/login").permitAll()
                .requestMatchers("/api/uploads/**").permitAll()
                .anyRequest().authenticated()
            );

        // 添加认证提供者
        // http.authenticationProvider(authenticationProvider());

        // 添加JWT认证过滤器,在Jwt过滤器中获取认证信息,然后将认证信息设置到SecurityContextHolder中
        http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenUtils, userDetailsService)
                ,UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

总结:核心配置类是 SpringSecurity配置类,然后其他4个类是自定义配置,过滤器是处理解析token的,剩下3各类,按照项目需要更改,如修改未认证,未授权异常情况的响应体,修改查询系统用户的逻辑。

0

评论区