SpringSecurity权限认证实战指南

Spring Security 权限认证实战指南

Spring Security 是 Java 后端安全认证与授权的事实标准,也是 CSDN Java 板块的高频话题。前面讲过 Spring 核心 IoC/AOP,这篇专门讲安全:认证流程、过滤器链、JWT 集成、方法级权限、密码加密、OAuth2、常见坑,全程带 Spring Boot 代码。


一、Spring Security 是什么

1.1 定位

Spring Security 提供**认证(你是谁)和授权(你能干什么)**两大能力:

  • 认证(Authentication):验证用户名密码、Token、OAuth 登录;
  • 授权(Authorization):角色/权限控制(哪些接口谁能访问);
  • 防护:CSRF、CORS、Session 固定、点击劫持。

1.2 为什么用它

  • 业界标准(Spring 官方出品),生态完整;
  • 过滤器链机制,扩展性强;
  • 声明式安全配置,少写代码。

二、快速开始

2.1 依赖

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

加入依赖后默认行为 :所有接口都要登录,默认用户 user,密码在启动日志里(随机生成)。

2.2 基础配置

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())                  // 前后端分离关 CSRF
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()   // 登录注册放行
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/user/**").hasRole("USER")
                .anyRequest().authenticated()              // 其余都要登录
            )
            .formLogin(form -> form.disable())             // 禁用表单登录
            .httpBasic(basic -> basic.disable());          // 禁用 Basic
        return http.build();
    }
}

2.3 内存用户(测试用)

java 复制代码
@Bean
public UserDetailsService userDetailsService() {
    UserDetails admin = User.withUsername("admin")
        .password(passwordEncoder().encode("123456"))
        .roles("ADMIN")
        .build();

    UserDetails user = User.withUsername("user")
        .password(passwordEncoder().encode("123456"))
        .roles("USER")
        .build();

    return new InMemoryUserDetailsManager(admin, user);
}

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

三、认证流程(过滤器链)

3.1 核心过滤器链

复制代码
请求
  → SecurityContextPersistenceFilter(恢复上下文)
  → UsernamePasswordAuthenticationFilter(表单登录)
  → BasicAuthenticationFilter(Basic 认证)
  → BearerTokenAuthenticationFilter(JWT/OAuth2 Token)
  → AnonymousAuthenticationFilter(匿名)
  → ExceptionTranslationFilter(异常转 401/403)
  → AuthorizationFilter(授权判断)
  → 业务接口

记住 :请求先过认证过滤器,再过授权过滤器。

3.2 认证核心组件

组件 作用
Authentication 认证信息(未认证/已认证)
AuthenticationManager 认证入口
AuthenticationProvider 具体认证逻辑(查库、校验密码)
UserDetailsService 按用户名查用户
SecurityContext 当前登录上下文
SecurityContextHolder 保存上下文的容器

3.3 从数据库认证(生产标准)

java 复制代码
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())          // 存的是 BCrypt 密文
            .roles(user.getRole())                 // "ADMIN" / "USER"
            .build();
    }
}

四、JWT 集成(前后端分离标准方案)

4.1 为什么用 JWT

  • 无状态:服务器不存会话,适合分布式/微服务;
  • 自包含:Token 里带用户信息和过期时间;
  • 跨域友好:放在 Authorization 头。

4.2 JWT 结构

复制代码
Header(算法).Payload(claims).Signature(签名)

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.签名
  • Header:算法 HS256;
  • Payload:sub(用户名)、iat(签发时间)、exp(过期时间)、角色;
  • Signature:Header + Payload + 密钥签名------防篡改。

4.3 登录接口生成 Token

java 复制代码
@Service
public class AuthService {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtUtil jwtUtil;

    public String login(LoginDTO dto) {
        // 1. 认证(校验用户名密码)
        Authentication auth = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                dto.getUsername(), dto.getPassword()));

        // 2. 认证成功 → 生成 token
        UserDetails user = (UserDetails) auth.getPrincipal();
        return jwtUtil.generateToken(user.getUsername());
    }
}
java 复制代码
// JwtUtil(简化版,生产用 jjwt 库)
@Component
public class JwtUtil {

    private static final SecretKey KEY =
        Keys.hmacShaKeyFor("your-secret-key-please-change-it-32bytes".getBytes());

    public String generateToken(String username) {
        return Jwts.builder()
            .setSubject(username)
            .claim("role", "ADMIN")
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600_000))  // 1h
            .signWith(KEY)
            .compact();
    }

    public String parseUsername(String token) {
        return Jwts.parserBuilder()
            .setSigningKey(KEY)
            .build()
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }
}

4.4 JWT 过滤器

java 复制代码
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private UserDetailsService userDetailsService;

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

        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            try {
                String username = jwtUtil.parseUsername(token);
                if (username != null
                        && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails user = userDetailsService.loadUserByUsername(username);
                    var auth = new UsernamePasswordAuthenticationToken(
                        user, null, user.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(auth);
                }
            } catch (Exception e) {
                // token 无效:不设置认证,走匿名
            }
        }
        chain.doFilter(request, response);
    }
}

4.5 注册过滤器

java 复制代码
// 在 SecurityConfig 里加
http.addFilterBefore(jwtAuthenticationFilter,
    UsernamePasswordAuthenticationFilter.class);

五、方法级权限

5.1 开启

java 复制代码
@Configuration
@EnableWebSecurity
@EnableMethodSecurity        // 开启方法级权限
public class SecurityConfig { }

5.2 注解使用

java 复制代码
@RestController
public class OrderController {

    @GetMapping("/orders")
    @PreAuthorize("hasRole('USER')")          // 需要 USER 角色
    public List<Order> myOrders() { ... }

    @DeleteMapping("/orders/{id}")
    @PreAuthorize("hasRole('ADMIN')")         // 需要 ADMIN 角色
    public void deleteOrder(@PathVariable Long id) { ... }

    @GetMapping("/profile")
    @PreAuthorize("hasAuthority('user:read')")  // 按权限点
    public Profile profile() { ... }
}

5.3 常用表达式

表达式 含义
hasRole('ADMIN') 有 ADMIN 角色
hasAnyRole('ADMIN','USER') 任一角色
hasAuthority('user:read') 有某权限点
#id == authentication.principal.id SpEL 比对参数
isAuthenticated() 已登录
permitAll() / denyAll() 全放行/全拒绝

5.4 拿到当前用户

java 复制代码
@GetMapping("/me")
public User me() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    return userService.findByUsername(auth.getName());
}

// 或参数注入
@GetMapping("/me2")
public User me2(@AuthenticationPrincipal UserDetails user) {
    return userService.findByUsername(user.getUsername());
}

六、密码安全

6.1 BCrypt(默认推荐)

java 复制代码
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();    // 自动加盐,不可逆
}

// 注册时加密存储
user.setPassword(passwordEncoder.encode(rawPassword));

// 登录时校验
boolean ok = passwordEncoder.matches(rawPassword, storedHash);

6.2 为什么不用 MD5/SHA

  • MD5/SHA 是"摘要"不是"密码哈希"------无盐可查彩虹表;
  • BCrypt 内置随机盐 + 计算慢(防暴力);
  • 现代替代:scrypt、Argon2。

七、OAuth2 / 第三方登录(可选)

7.1 概念

OAuth2 让用户"授权"第三方应用访问其资源,而不交出密码。

复制代码
用户 → 授权服务器(微信/GitHub)→ 授权码
     → 应用服务器用授权码 + client_secret 换 token
     → 用 token 调资源接口拿用户信息

7.2 Spring Boot 集成

yaml 复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: xxx
            client-secret: xxx
java 复制代码
http.oauth2Login(oauth -> oauth
    .defaultSuccessUrl("/", true));

企业内常用:OAuth2 + 微信/钉钉扫码登录(前面有钉钉插件能力)。


八、常见坑速查

现象 原因 解决
加了依赖全站要登录 默认安全策略 配置 permitAll
密码错误但明明对 密码不是 BCrypt 用 encoder.matches
登录后 403 角色前缀问题 hasRole 自动加 ROLE_
JWT 过滤器不执行 没注册 addFilterBefore
自定义接口 404 放行了但没权限 检查 matcher 顺序
改配置不生效 缓存 重启/清缓存
前后端分离 403 CSRF 关 csrf 或配白名单
方法权限不生效 没开 @EnableMethodSecurity 开启注解

本章小结

Spring Security 的心智模型:过滤器链做认证,AuthorizationFilter 做授权,SecurityContext 存当前用户 。前后端分离项目的标准组合 = JWT 过滤器 + BCrypt 密码 + 方法级 @PreAuthorize。记住角色有 ROLE_ 前缀、密码必须 BCrypt、Token 必须有过期时间这三个要点,就能避免 90% 的坑。

下一篇讲 FastAPI------Python 后端开发的高效选择。

(全文完)

相关推荐
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: 学习路径与命令速查导引
java·学习·docker
tachibana21 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
九零HTTP1 小时前
从 Cookie 到 Token,登录态到底是怎么维持的
后端
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: Docker 镜像仓库
java·docker
程序边界2 小时前
用了三年数据同步软件,我想好好聊聊KFS这套东西到底强在哪(上)
后端
sp422 小时前
简单的 Spring 文件上传与下载组件
后端
企智汇-项目管理软件2 小时前
离散制造管理系统怎么选?一套系统管住从研发到交付的全周期
java·人工智能·制造·项目经理·项目管理软件·项目管理系统·项目管理软件工程
量化分析码农2 小时前
【Python量化实盘工程 #04】回测收益虚高30%?滑点与冲击成本建模让模拟更真实
后端
QuantiCore_IO2 小时前
量化回测复权数据的工程实践:从 API 获取到数据质量验证
后端·github·api