Spring Security认证授权深度解析

一 Spring Security简介

Spring Security是Spring生态系统中的一个安全框架,主要用于处理认证(Authentication)和授权(Authorization)。它提供了一套完整的安全解决方案,可以轻松集成到Spring应用中。

二 核心概念

1. 认证(Authentication)

验证用户的身份,确认"你是谁"。例如:用户登录过程。

2. 授权(Authorization)

验证用户是否有权限执行某个操作,确认"你能做什么"。例如:检查用户是否有权访问某个API。

3. 主要组件

  • SecurityContextHolder:存储安全上下文信息
  • Authentication:存储当前用户的认证信息
  • UserDetails:用户信息的核心接口
  • UserDetailsService:加载用户信息的核心接口
  • AuthenticationProvider:认证的具体实现者

三 实战案例:基于JWT的认证授权系统

一、核心架构

1. 核心组件关系图

复制代码
请求 → SecurityFilterChain → (多个Security Filter) → 目标资源
                                     ↓
                           SecurityContextHolder
                                     ↓
                              SecurityContext
                                     ↓
                             Authentication
                            /            \
                     Principal     GrantedAuthority

2. 核心组件说明

2.1 SecurityContextHolder
  • 作用:存储当前线程的安全上下文信息
  • 实现:使用ThreadLocal存储SecurityContext
  • 访问方式:
java 复制代码
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
2.2 SecurityContext
  • 作用:持有Authentication对象和其他安全相关信息
  • 生命周期:请求开始到结束
  • 存储位置:ThreadLocal或Session中
2.3 Authentication
  • 作用:存储用户认证信息
  • 主要属性:
    • principal:用户身份信息
    • credentials:凭证信息(如密码)
    • authorities:用户权限集合
    • authenticated:是否已认证
  • 常用实现:UsernamePasswordAuthenticationToken

二、认证流程详解

1. 完整认证流程图

复制代码
用户请求登录
     ↓
UsernamePasswordAuthenticationFilter
     ↓
AuthenticationManager
     ↓
AuthenticationProvider
     ↓
UserDetailsService
     ↓
UserDetails
     ↓
Authentication对象
     ↓
SecurityContext

2. 详细流程说明

2.1 认证入口(以登录为例)
java 复制代码
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
    // 1. 创建未认证的Authentication
    Authentication authentication = new UsernamePasswordAuthenticationToken(
        loginRequest.getUsername(),
        loginRequest.getPassword()
    );

    // 2. 执行认证
    Authentication authenticated = authenticationManager.authenticate(authentication);

    // 3. 认证成功,生成JWT
    SecurityContextHolder.getContext().setAuthentication(authenticated);
    String jwt = tokenProvider.generateToken(authenticated);

    return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
2.2 认证管理器(AuthenticationManager)
java 复制代码
public class ProviderManager implements AuthenticationManager {
    private List<AuthenticationProvider> providers;

    @Override
    public Authentication authenticate(Authentication authentication) {
        // 遍历所有Provider尝试认证
        for (AuthenticationProvider provider : providers) {
            if (!provider.supports(authentication.getClass())) {
                continue;
            }
            
            try {
                return provider.authenticate(authentication);
            } catch (AuthenticationException e) {
                // 处理认证异常
            }
        }
        throw new AuthenticationException("无法认证");
    }
}
2.3 自定义认证提供者
java 复制代码
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    private final CustomUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) {
        // 1. 获取认证信息
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // 2. 加载用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        // 3. 验证密码
        if (!passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("密码错误");
        }

        // 4. 创建已认证的Authentication
        return new UsernamePasswordAuthenticationToken(
            userDetails,
            null,
            userDetails.getAuthorities()
        );
    }
}

三、授权流程详解

1. 授权流程图

复制代码
请求 → FilterSecurityInterceptor
         ↓
    SecurityContextHolder获取Authentication
         ↓
    AccessDecisionManager
         ↓
    AccessDecisionVoter
         ↓
    权限判断结果

2. 详细授权步骤

2.1 配置安全规则
java 复制代码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http.authorizeRequests()
            // 1. URL级别的权限控制
            .antMatchers("/api/public/**").permitAll()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            // 2. 自定义权限判断
            .anyRequest().access("@customSecurityService.hasPermission(request,authentication)");
    }
}
2.2 方法级别权限控制
java 复制代码
@Service
public class UserService {
    // 使用Spring EL表达式进行权限控制
    @PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
    public UserDetails getUser(String username) {
        // 方法实现
    }
}

四、JWT集成原理

1. JWT认证流程

复制代码
请求 → JwtAuthenticationFilter
         ↓
    提取JWT令牌
         ↓
    验证JWT有效性
         ↓
    解析用户信息
         ↓
    创建Authentication
         ↓
    存入SecurityContext

2. JWT过滤器实现

java 复制代码
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) {
        try {
            // 1. 从请求中提取JWT
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                // 2. 从JWT中获取用户信息
                String username = tokenProvider.getUsernameFromJWT(jwt);
                String roles = tokenProvider.getRolesFromJWT(jwt);

                // 3. 创建Authentication
                List<GrantedAuthority> authorities = Arrays.stream(roles.split(","))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());

                UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(username, null, authorities);

                // 4. 设置认证信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("无法设置用户认证", ex);
        }

        filterChain.doFilter(request, response);
    }
}

五、数据校验流程

1. 请求数据校验

java 复制代码
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
    // 1. @Valid触发数据校验
    // 2. 校验失败抛出MethodArgumentNotValidException
}

public class LoginRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;
}

2. 认证数据校验

java 复制代码
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        // 1. 检查用户是否存在
        UserPrincipal user = userMap.get(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2. 检查用户状态
        if (!user.isEnabled()) {
            throw new DisabledException("用户已禁用");
        }

        // 3. 检查账户是否过期
        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("账户已过期");
        }

        // 4. 检查账户是否锁定
        if (!user.isAccountNonLocked()) {
            throw new LockedException("账户已锁定");
        }

        return user;
    }
}

3. JWT数据校验

java 复制代码
public class JwtTokenProvider {
    public boolean validateToken(String authToken) {
        try {
            // 1. 验证签名
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            
            // 2. 验证是否过期
            Claims claims = getClaimsFromJWT(authToken);
            return !claims.getExpiration().before(new Date());
            
        } catch (SignatureException ex) {
            logger.error("无效的JWT签名");
        } catch (MalformedJwtException ex) {
            logger.error("无效的JWT令牌");
        } catch (ExpiredJwtException ex) {
            logger.error("JWT令牌已过期");
        } catch (UnsupportedJwtException ex) {
            logger.error("不支持的JWT令牌");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT声明为空");
        }
        return false;
    }
}

六、异常处理流程

1. 认证异常处理

java 复制代码
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                        HttpServletResponse response,
                        AuthenticationException e) throws IOException {
        // 1. 未认证异常处理
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        
        String message = "请先进行认证";
        if (e instanceof BadCredentialsException) {
            message = "用户名或密码错误";
        } else if (e instanceof JwtExpiredTokenException) {
            message = "token已过期";
        }
        
        response.getWriter().write(
            new ObjectMapper().writeValueAsString(
                new ApiResponse(false, message)
            )
        );
    }
}

2. 授权异常处理

java 复制代码
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request,
                      HttpServletResponse response,
                      AccessDeniedException e) throws IOException {
        // 1. 权限不足异常处理
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        
        response.getWriter().write(
            new ObjectMapper().writeValueAsString(
                new ApiResponse(false, "没有足够的权限")
            )
        );
    }
}

七、安全上下文传递

1. 异步方法中的安全上下文

java 复制代码
@Async
public CompletableFuture<String> asyncMethod() {
    // 1. 获取当前安全上下文
    SecurityContext context = SecurityContextHolder.getContext();
    
    return CompletableFuture.supplyAsync(() -> {
        try {
            // 2. 设置安全上下文到新线程
            SecurityContextHolder.setContext(context);
            // 3. 执行业务逻辑
            return "success";
        } finally {
            // 4. 清理安全上下文
            SecurityContextHolder.clearContext();
        }
    });
}
相关推荐
RainbowSea1 小时前
问题:后端由于字符内容过长,前端展示精度丢失修复
java·spring boot·后端
C182981825751 小时前
OOM电商系统订单缓存泄漏,这是泄漏还是溢出
java·spring·缓存
风象南1 小时前
SpringBoot 控制器的动态注册与卸载
java·spring boot·后端
我是一只代码狗2 小时前
springboot中使用线程池
java·spring boot·后端
hello早上好2 小时前
JDK 代理原理
java·spring boot·spring
PanZonghui2 小时前
Centos项目部署之运行SpringBoot打包后的jar文件
linux·spring boot
沉着的码农2 小时前
【设计模式】基于责任链模式的参数校验
java·spring boot·分布式
zyxzyx6662 小时前
Flyway 介绍以及与 Spring Boot 集成指南
spring boot·笔记
何苏三月3 小时前
SpringCloud系列 - Sentinel 服务保护(四)
spring·spring cloud·sentinel
纳兰青华3 小时前
bean注入的过程中,Property of ‘java.util.ArrayList‘ type cannot be injected by ‘List‘
java·开发语言·spring·list