SpringSecurity认证和权限

一、核心功能

在企业级 Java 开发中,Spring Security 是保护系统的绝对核心框架,我经常使用它来处理用户认证( Authentication 、权限授权(Authorization)以及安全防护(如防御 CSRF、CORS、会话固定攻击等)。

它的底层核心本质是基于一系列的 Servlet 过滤器链(FilterChain),在请求到达你的 Controller 之前进行拦截和校验。

1、 核心架构与落地实现方案

在实际的商业项目中,目前最标准的落地做法是 Spring Boot + Spring Security + JWT JSON Web Token 实现前后端分离的无状态认证。以下是具体实现的四大核心步骤:

1.1. 引入依赖

在 pom.xml 中引入 Spring Security 官方起步依赖:

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

1.2. 自定义用户数据源(重写认证逻辑)

Spring Security 默认生成一个随机密码,实际开发中我们需要对接数据库。通过实现 UserDetailsService 接口来加载数据库中的用户信息:

java 复制代码
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper; // 你的数据库查询 Mapper

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 从数据库查询用户
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2. 查询用户的权限列表(例如:ROLE_ADMIN, sys:user:delete)
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles());

        // 3. 返回 Spring Security 需要的 UserDetails 对象
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(), user.getPassword(), authorities);
    }
}

1.3. 核心安全配置( SecurityFilterChain

在 Spring Boot 3.x / Spring Security 6.x 中,全面废弃了原有的继承基类写法,改用基于 Bean 的链式配置

java 复制代码
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 开启方法级别的权限注解
public class SecurityConfig {
    @Autowired
    private JwtAuthenticationTokenFilter jwtFilter; // 自定义的 JWT 拦截器

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 1. 关闭 CSRF(因为前后端分离使用 JWT,不需要 Session 防御)
        http.csrf(csrf -> csrf.disable()) 
            // 2. 管理请求的准入规则
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/register", "/public/**").permitAll() // 匿名放行
                .anyRequest().authenticated() // 其余所有请求必须认证
            )
            // 3. 前后端分离,不创建 Session(改为无状态)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 4. 将自定义的 JWT 过滤器加入到 UsernamePasswordAuthenticationFilter 之前
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    // 配置密码加密器(使用强哈希算法,注册时用它加密,登录时 Security 会自动用它比对)
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

1.4. 自定义 JWT 过滤器(承上启下)

除了 /login 接口,用户访问其他接口都会携带 Token。我们需要写一个过滤器,每次请求来的时候解析 Token,并把用户信息塞给 Spring Security 的上下文:

java 复制代码
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // 1. 从请求头获取 Token
        String token = request.getHeader("Authorization");

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response); // 没有 token 放行给后面的过滤器去报错
            return;
        }

        // 2. 解析 Token 拿到用户名和权限(解析过程略)
        String username = JwtUtil.parseToken(token.replace("Bearer ", ""));

        // 3. 如果 Token 有效,手动构建 Authentication 对象塞入上下文中
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);

            UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

            // 关键点:绑定到当前线程上下文,后续的拦截器或 Controller 就能直接识别到已登录
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }
}

2、权限控制的精准落地(如何进行资源鉴权)

做完上述配置后,Spring Security 提供了极其优雅的注解式权限控制。在 Controller 的方法上直接贴注解即可:

  • 基于角色控制:
java 复制代码
@GetMapping("/admin/data")
@PreAuthorize("hasRole('ADMIN')") // 必须拥有 ROLE_ADMIN 角色才能访问
public ResponseResult getAdminData() { return ResponseResult.success(); }
  • 基于按钮权限码控制(更常用):
java 复制代码
@DeleteMapping("/user/{id}")
@PreAuthorize("hasAuthority('sys:user:delete')") // 必须拥有该具体权限
public ResponseResult deleteUser(@PathVariable Long id) { return ResponseResult.success(); }

3、 核心踩坑与实战经验

  1. Spring Security 基本上是线程绑定的: 它利用 ThreadLocal 存储登录态(SecurityContextHolder)。坑点: 如果你在 Controller 里开了异步子线程(如 @Async 或 CompletableFuture),子线程会拿不到登录用户信息。解决办法: 配置 SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); 让子线程继承父线程的上下文。
  2. 异常捕获问题: 如果未登录或没权限,Spring Security 会在过滤器层抛出异常(如 AccessDeniedException),此时你在 Controller 层的 @RestControllerAdvice 全局异常处理器是拦截不到的解决办法: 必须自定义实现 AuthenticationEntryPoint(未登录处理器)和 AccessDeniedHandler(无权限处理器),然后配置到 HttpSecurity 中。

二、身份认证和鉴权认证

在 Spring Boot 中,身份认证( Authentication 鉴权认证 / 授权( Authorization 安全防线的两个不同阶段,通常借助 Spring Security 框架实现。

1. 身份认证( Authentication ------ " 你是谁? "

这是核实用户提交的凭证(如账号密码、短信验证码)是否真实有效的过程。

  • 捕获请求:用户发送登录请求,框架的过滤器(如 UsernamePasswordAuthenticationFilter)捕获请求并提取账号密码。
  • 加载数据:通过实现 UserDetailsService 接口,根据用户名去数据库中查询出该用户真实的密码、状态以及拥有的权限列表。
  • 密码比对:调用 PasswordEncoder(如 BCryptPasswordEncoder)对前端传入的明文密码与数据库中加密后的密文进行安全比对。
  • 签发凭证 :比对成功后,在内存/Redis 中生成 Session,或者为用户签发一个 JWT Token 返回给前端。

2. 鉴权认证( Authorization ------ " 你能做什么? "

在确认用户身份后,检查该用户是否拥有访问当前特定接口或资源的权限。

  • 配置拦截规则:在配置类中定义哪些 URL 需要什么权限(如 .requestMatchers("/admin/**").hasRole("ADMIN"))。
  • 注解精准控制:在 Controller 方法上直接贴权限注解(如 @PreAuthorize("hasAuthority('sys:user:delete')"))。
  • 匹配放行:每次请求进来,框架解析出当前登录用户的权限列表。如果列表中包含接口要求的权限码则放行,否则直接拦截并返回 403 Forbidden。

三、 身份认证流程(Authentication - 登录阶段)

这个阶段的目的是验证账密,并颁发 JWT 令牌

复制代码
前端 (账密) ──> 1. 提交登录请求 ──> 2. UsernamePasswordAuthenticationFilter 拦截
                                                    │
                                                    ▼
   生成 JWT <── 5. 登录成功处理器 <── 4. 验证成功 <── 3. AuthenticationManager (查库比对)
   返回前端      (AuthenticationSuccessHandler)
  1. 前端提交 :前端通过 POST 请求提交用户名和密码(例如 /api/login)。
  2. 过滤器拦截 :请求到达 Spring Security 的 UsernamePasswordAuthenticationFilter
  3. 经理去验签 :过滤器将账密封装成一个 UsernamePasswordAuthenticationToken 对象,传递给 AuthenticationManager(认证管理器)。
  4. 查库比对
    • AuthenticationManager 调用自定义的 UserDetailsService
    • UserDetailsService 从数据库中查询出用户信息、加密后的密码以及用户的权限/角色列表
    • 框架使用 PasswordEncoder 比对前端输入的密码与数据库密码是否一致。
  5. 成功并颁发 JWT
    • 比对成功后,触发 AuthenticationSuccessHandler(登录成功处理器)。
    • 在处理器中,调用 JWT 工具类,将用户的 ID、用户名、角色/权限列表 作为 Payload(载荷),加上秘钥进行签名,生成一个加密字符串(JWT Token)。
    • 将该 Token 放入 JSON 数据中返回给前端。服务器不保存任何 Session。

四、 鉴权认证流程(Authorization - 携带 Token 请求阶段)

用户登录后,后续请求任何受保护的接口(如 /api/user/delete),都必须携带 JWT。

复制代码
前端 (带Token) ──> 1. JwtAuthenticationFilter (提取/解析 Token)
                            │
              ┌─────────────┴─────────────┐
              ▼ (合法)                    ▼ (非法/过期/无Token)
       2. 查 Redis 黑名单             直接放行 (交给后续过滤器拦截)
              │
              ▼ (通过)
   3. 封装出系统内部的 Authentication
   4. 塞入 SecurityContextHolder
              │
              ▼
   5. 权限决策管理器 (AccessDecisionManager) ──> 对比 [接口所需权限] 与 [JWT解析出的权限]
              │
       ┌──────┴──────┐
       ▼ (匹配)      ▼ (不匹配)
    6. 放行       7. 触发异常处理器 (AccessDeniedHandler) -> 返回 403
  进入Controller
  1. 自定义过滤器拦截 :请求携带 Authorization: Bearer <Token> 到达网关或后端。自定义的 JwtAuthenticationFilter (通常继承自 OncePerRequestFilter)会优先拦截该请求。
  2. 提取与解析
    • 过滤器从 Header 中截取到 JWT 字符串。
    • 使用服务器密钥对 JWT 进行验签和解密。如果 Token 过期或被篡改,直接抛出异常,或交给后续过滤器处理。
  3. 黑名单校验(可选) :如果项目有强退或登出功能,过滤器会拿着 JWT 的唯一标识(如 jti)去 Redis 查询。如果在黑名单中,则拒绝访问。
  4. 构建上下文(认人)
    • 验签通过后,从 JWT 中解析出用户名(Username)和权限列表(Authorities)。
    • 将这些信息重新封装进 Spring Security 内部的 UsernamePasswordAuthenticationToken 对象中。
    • 执行 SecurityContextHolder.getContext().setAuthentication(auth),将该对象塞入全局上下文中。这相当于告诉 Spring Security:"这个请求的身份我已经核实过了,他是合法用户,权限都在这里"。
  5. 权限匹配(对暗号)
    • 请求继续向下走,到达 Spring Security 的权限拦截器(如 FilterSecurityInterceptor)。
    • 拦截器检查当前访问的方法或 URL 需要什么权限(例如:需要 user:delete 权限)。
    • 拦截器去 SecurityContextHolder 中取出刚才塞进去的权限列表进行比对。
  6. 最终结果
    • 权限匹配:成功放行,请求真正到达 Controller 层,执行业务逻辑。
    • 权限不匹配 :拒绝访问,触发 AccessDeniedHandler,向前端返回 403 Forbidden 状态码。
    • 未登录/Token无效 :若上下文为空,触发 AuthenticationEntryPoint,向前端返回 401 Unauthorized 状态码。

五、Gateway 网关具体做了什么认证?

在微服务架构中,Spring Cloud Gateway (网关) 作为全局唯一入口,承担了第一道防线的职责。它主要负责以下认证工作:

  • 全局白名单放行:网关直接拦截所有请求,匹配路由。如果是 /login、/register、/public/** 等公开接口,直接放行,不走任何认证逻辑。
  • Token 有效性粗校验 :拦截非白名单请求,检查 HTTP Header 中是否携带了 Authorization 标签,并在网关层对 Token 进行签名合法性过期时间的初步校验。如果 Token 是伪造的或已过期,网关直接阻断请求,返回 401 Unauthorized。
  • 动态黑名单拦截:网关通常会接入 Redis。当用户注销登录、改密码或被后台强制踢下线时,其 Token 会被加入 Redis 黑名单。网关在转发前会先查 Redis,若命中黑名单则直接拦截。
  • 身份信息透传(解耦核心) :网关校验 Token 成功后将其解密,提取出里面的 userId、username、roles 等核心信息,然后将这些信息放入请求头(如 X-User-Id)中,再把请求转发给下游的业务微服务(如订单服务、用户服务)。

价值:下游微服务不需要再做复杂的 Token 解密,直接从请求头拿用户 ID 即可,实现了鉴权逻辑与业务逻辑的解耦。

六、JWT 认证校验了哪几个层面的东西?

一个标准的 JWT Token(由 Header.Payload.Signature 三部分组成)在被后端拦截器校验时,必须严格通过以下 4 个层面的检查:

  • 层面 1 :结构与格式合法性( Format
    • 校验 Token 是否严格包含两个点 . 并能将其切分为三段。格式错误或缺少段落的 Token 直接判定非法。
  • 层面 2 :数字签名防篡改( Signature ------ 最关键
    • 后端获取 Token 的第三段(Signature),利用服务器只有自己知道的 密钥( Secret Key ,对前两段(Header + Payload)重新进行哈希计算(如 HMAC-SHA256)。
    • 比对本地计算结果与 Token 自带的第三段是否完全一致。如果一致,证明 Token 内容绝对没有被前端或中间人篡改过
  • 层面 3 :时效性检查( Expiration
    • 解析第二段(Payload)中的 exp(过期时间戳)和 nbf(生效时间戳)。
    • 比对服务器当前系统时间。如果当前时间大于 exp,直接判定 Token 已过期,拒绝访问。
  • 层面 4 :业务状态与黑名单校验( Business Status
    • 解密出 Payload 中的 userId 或 jti(Token 唯一标识)。
    • 去 Redis 中比对该 Token 是否存在于黑名单 / 销毁列表中(如上述的踢下线场景)。

同时可以校验该用户的状态是否正常(如:该账号是否在 5 分钟前刚刚被管理员冻结)。如果命中了黑名单或用户状态异常,则校验失败。

相关推荐
Devin~Y5 天前
互联网大厂 Java 面试实录:Spring Boot、MyBatis、Redis、Kafka、Spring Security、RAG 与 MCP 全链路问答
java·redis·kafka·mybatis·spring security·spring mvc·sprint boot
Devin~Y12 天前
互联网大厂Java面试实战:Spring Boot、MyBatis、Redis、Kafka、JWT、Spring Cloud 与 AI 场景追问
java·spring boot·redis·spring cloud·kafka·mybatis·spring security
k↑24 天前
Spring gRPC集成Spring Security OAuth2
spring security·spring sgrpc
西凉的悲伤2 个月前
Spring Security + JWT 登录认证完整实践指南
java·后端·spring·spring security·jwt
Micro麦可乐2 个月前
最新Spring Security实战教程(十)权限表达式进阶 - 在SpEL在安全控制中的高阶魔法
java·spring boot·后端·spring·spring security·spel表达式
消失的旧时光-19432 个月前
企业认证与安全体系(五):Spring Security + JWT + Redis 企业级认证实战
redis·安全·spring·spring security·jwt
消失的旧时光-19432 个月前
企业认证与安全体系(四):企业登录认证流程全解析——JWT、Redis、Spring Security 如何协同工作?
redis·安全·spring·spring security·jwt
智研数智工坊2 个月前
SpringBoot4.0.6 + Security7.x + JWT 最新完整实战|无状态权限认证、统一异常处理、可直接落地
java·spring boot·spring security·jwt·权限认证
段ヤシ.2 个月前
回顾Java知识点,面试题汇总Day17(持续更新)
java·springboot·spring security·shiro·mybatis-plus·jdbctemplate·spring data jpa