Spring Security 原理 + 生产环境认证授权实战

目录

[一、Spring Security 核心原理(底层到底怎么工作?)](#一、Spring Security 核心原理(底层到底怎么工作?))

[1. 核心流程(一句话版)](#1. 核心流程(一句话版))

[2. 核心组件(必须记住)](#2. 核心组件(必须记住))

[3. 标准认证流程(生产环境通用)](#3. 标准认证流程(生产环境通用))

[二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权)](#二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权))

技术栈

[步骤 1:引入依赖](#步骤 1:引入依赖)

[步骤 2:数据库表设计(生产标准)](#步骤 2:数据库表设计(生产标准))

[步骤 3:核心配置类(SecurityConfig)](#步骤 3:核心配置类(SecurityConfig))

[步骤 4:JWT 工具类(生成 / 解析 Token)](#步骤 4:JWT 工具类(生成 / 解析 Token))

[步骤 5:实现 UserDetailsService(加载数据库用户)](#步骤 5:实现 UserDetailsService(加载数据库用户))

[步骤 6:JWT 认证过滤器(核心)](#步骤 6:JWT 认证过滤器(核心))

[步骤 7:异常处理(未登录 / 权限不足)](#步骤 7:异常处理(未登录 / 权限不足))

[7.1 未登录处理](#7.1 未登录处理)

[7.2 权限不足处理](#7.2 权限不足处理)

[步骤 8:登录接口(控制器)](#步骤 8:登录接口(控制器))

[步骤 9:权限控制使用](#步骤 9:权限控制使用)

[方式 1:配置类控制(推荐)](#方式 1:配置类控制(推荐))

[方式 2:注解控制(灵活)](#方式 2:注解控制(灵活))

三、生产环境关键要点(必看)

四、完整流程总结

总结


本文先讲原理(底层怎么跑)→ 再讲生产实战(代码怎么写) 的方式,带你彻底吃透 Spring Security。


一、Spring Security 核心原理(底层到底怎么工作?)

Spring Security 本质是基于 Servlet 过滤器链的安全框架,核心做两件事:

  1. 认证:你是谁(登录校验)
  2. 授权:你能做什么(权限校验)

1. 核心流程(一句话版)

请求 → 过滤器链 → 认证 / 授权校验 → 合法放行 / 非法拦截

2. 核心组件(必须记住)

组件 作用
SecurityContextHolder 存储当前登录用户信息(全局获取用户)
Authentication 认证对象(封装用户信息、权限、认证状态)
UserDetailsService 核心接口:加载数据库用户信息(你必须实现)
PasswordEncoder 密码加密器(BCrypt 算法)
FilterSecurityInterceptor 权限拦截最终过滤器
OncePerRequestFilter 自定义过滤器基类(JWT 必用)

3. 标准认证流程(生产环境通用)

  1. 用户提交账号密码 → 控制器接收
  2. 调用 UserDetailsService 从数据库查用户
  3. 比对密码(PasswordEncoder
  4. 认证通过 → 生成 JWT Token 返回给前端
  5. 前端每次请求携带 Token → 自定义过滤器解析 Token
  6. 封装用户信息存入 SecurityContextHolder
  7. 请求到达接口 → 校验权限
  8. 响应结果

二、生产环境实战方案(前后端分离 + JWT + 数据库认证授权)

技术栈

  • Spring Boot 3.x / Spring Security 6.x
  • JWT(jjwt)
  • MySQL + MyBatis/MyBatis-Plus
  • 前后端分离(无 Session)

步骤 1:引入依赖

复制代码
<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT -->
<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>

<!-- MySQL + MyBatis-Plus -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

步骤 2:数据库表设计(生产标准)

最少需要 3 张表:用户表、角色表、用户角色关联表(RBAC 权限模型)

复制代码
-- 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    status TINYINT DEFAULT 1, -- 1正常 0禁用
    create_time DATETIME DEFAULT NOW()
);

-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_code VARCHAR(50) NOT NULL UNIQUE, -- ROLE_ADMIN、ROLE_USER
    role_name VARCHAR(50) NOT NULL
);

-- 用户-角色关联表
CREATE TABLE sys_user_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL
);

步骤 3:核心配置类(SecurityConfig)

这是整个框架的控制中心,配置放行接口、过滤器、异常处理、权限规则。

复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    // 密码加密器(必须配置)
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 前后端分离:关闭csrf和session
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

                // 配置权限规则
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/user/login", "/user/register").permitAll() // 放行登录注册
                        .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // 放行接口文档
                        .requestMatchers("/admin/**").hasRole("ADMIN") // 管理员接口
                        .anyRequest().authenticated() // 其他所有接口都需要认证
                )

                // 异常处理
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint(jwtAuthenticationEntryPoint) // 未登录处理
                        .accessDeniedHandler(jwtAccessDeniedHandler) // 权限不足处理
                )

                // 添加JWT过滤器
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

步骤 4:JWT 工具类(生成 / 解析 Token)

复制代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

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

    @Value("${jwt.expiration}")
    private Long expiration;

    // 生成密钥
    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    // 从token中获取用户名
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    // 生成token
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSigningKey())
                .compact();
    }

    // 验证token
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

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

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

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

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

配置文件 application.yml

复制代码
jwt:
  secret: yourSecretKeyyourSecretKeyyourSecretKeyyourSecretKey # 32位以上
  expiration: 86400000 # 1天

步骤 5:实现 UserDetailsService(加载数据库用户)

Spring Security 认证的核心扩展点,你只需要实现这个接口,框架自动完成认证。

复制代码
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final SysUserMapper userMapper;
    private final SysRoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 根据用户名查用户
        SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUsername, username));
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2. 查询用户角色
        List<SysRole> roles = roleMapper.selectRolesByUserId(user.getId());
        List<SimpleGrantedAuthority> authorities = roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getRoleCode())) // ROLE_ADMIN
                .collect(Collectors.toList());

        // 3. 返回Security需要的User对象
        return new User(
                user.getUsername(),
                user.getPassword(),
                user.getStatus() == 1, // 账号状态
                true, true, true,
                authorities
        );
    }
}

步骤 6:JWT 认证过滤器(核心)

每次请求都会经过这个过滤器,解析 Token → 认证用户 → 存入上下文

复制代码
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. 获取请求头中的token
        String authHeader = request.getHeader("Authorization");
        String username = null;
        String jwtToken = null;

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            jwtToken = authHeader.substring(7);
            username = jwtUtil.extractUsername(jwtToken);
        }

        // 2. 验证token
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtUtil.validateToken(jwtToken, userDetails)) {
                // 3. 认证通过,将用户信息存入上下文
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);
    }
}

步骤 7:异常处理(未登录 / 权限不足)

生产环境必须返回标准 JSON,而不是默认页面。

7.1 未登录处理
复制代码
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("{\"code\":401,\"msg\":\"未登录或token已过期\"}");
    }
}
7.2 权限不足处理
复制代码
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("{\"code\":403,\"msg\":\"权限不足\"}");
    }
}

步骤 8:登录接口(控制器)

复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final JwtUtil jwtUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginDTO loginDTO) {
        // 1. 认证
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword())
        );

        // 2. 生成token
        UserDetails userDetails = userDetailsService.loadUserByUsername(loginDTO.getUsername());
        String token = jwtUtil.generateToken(userDetails);

        // 3. 返回
        return ResponseEntity.ok("登录成功,token:" + token);
    }
}

必须手动注入 AuthenticationManager

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

步骤 9:权限控制使用

方式 1:配置类控制(推荐)
复制代码
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("ADMIN", "USER")
方式 2:注解控制(灵活)

启动类开启注解:

复制代码
@EnableMethodSecurity

接口使用:

复制代码
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/list")
public List<SysUser> adminList() {
    return userMapper.selectList(null);
}

三、生产环境关键要点(必看)

  1. 密码必须加密 :注册时使用 passwordEncoder.encode(password) 存入数据库
  2. 无状态设计:完全禁用 Session,全部使用 JWT
  3. Token 安全:设置过期时间,密钥足够复杂,前端存储在 localStorage
  4. 权限模型:严格使用 RBAC(用户→角色→权限)
  5. 异常统一:未登录 401、权限不足 403,返回 JSON 格式
  6. 放行规则:接口文档、登录、注册、静态资源必须放行
  7. 跨域配置:前后端分离必须配置跨域

四、完整流程总结

  1. 用户登录 → 校验账号密码 → 生成 JWT 返回
  2. 前端携带 JWT 访问接口
  3. JWT 过滤器解析 Token → 加载用户 → 存入上下文
  4. Spring Security 校验权限
  5. 合法放行,非法返回 401/403

总结

  1. Spring Security 核心:过滤器链 + 认证(Authentication)+ 授权(Authorization)
  2. 核心扩展点UserDetailsService(加载数据库用户)
  3. 生产标准方案:前后端分离 + JWT + RBAC + 无状态
  4. 关键流程:登录生成 Token → 请求校验 Token → 权限拦截
相关推荐
憧憬成为java架构高手的小白1 小时前
黑马八股准备篇
spring
wand codemonkey1 小时前
【第五步+前后分离调】最后的联动调试--java+Vue3项目
java·开发语言·vue.js
东方小月1 小时前
Claude Code Skill 完全指南:一个 markdown 文件,就是一个专家分身
前端·后端
JunLa1 小时前
L angGraph vs 链式调用
java·网络·数据库
DianSan_ERP2 小时前
抖店订单接口中消费者信息加密解密机制与安全履约全解析
前端·网络·数据库·后端·安全·团队开发·运维开发
晚风烟火2 小时前
从“落地实践”和“应试通关”两个维度,拆解每一章到底要掌握什么
java
ps酷教程2 小时前
jackson学习
java·学习
紫洋葱_popo2 小时前
一文吃透 LangChain 流式输出:同步、异步、LCEL 链式穿透全解析
后端
松就是我902982 小时前
LLM 代理服务实现原理文档
后端