Spring Security

package com.example.securitydemo.config;

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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration  // 标记为Spring配置类,让Spring加载
@EnableWebSecurity  // 启用Spring Security的Web安全功能(必须加)
@EnableMethodSecurity  // 启用方法级权限控制(如@PreAuthorize注解)
public class SecurityConfig {

    // 注入用户信息服务(从数据库/内存加载用户,自定义实现)
    private final UserDetailsService userDetailsService;

    // 构造器注入(推荐,替代@Autowired)
    public SecurityConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }


    // --------------- 1. 密码加密器(必须配置!)---------------
    /**
     * 作用:指定密码的加密/验证方式(Spring Security强制要求)
     * 为什么需要?:防止密码明文存储,BCrypt是官方推荐算法(自动加盐,不可逆)
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // 其他可选:NoOpPasswordEncoder(明文,仅测试用)
    }


    // --------------- 2. 认证提供者(连接用户服务和密码加密器)---------------
    /**
     * 作用:将「用户信息服务」和「密码加密器」关联,负责实际的认证逻辑
     * 流程:接收用户名密码 → 调用userDetailsService查用户 → 用passwordEncoder验证密码
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService); // 设置用户信息来源
        provider.setPasswordEncoder(passwordEncoder());     // 设置密码验证方式
        return provider;
    }


    // --------------- 3. 认证管理器(认证入口)---------------
    /**
     * 作用:提供认证入口(如登录时调用其authenticate方法触发认证)
     * 场景:自定义登录接口(如前后端分离的JSON登录)时需要注入使用
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }


    // --------------- 4. 核心:HTTP安全规则配置(最常用)---------------
    /**
     * 作用:配置「URL访问权限」「登录/退出行为」「CSRF防护」等核心安全规则
     * 类比:小区保安的具体工作细则(谁能进哪栋楼,怎么登记,怎么离开)
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // ① 配置URL访问权限(核心!)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()  // 公开接口:所有人可访问
                .requestMatchers("/admin/**").hasRole("ADMIN")  // 管理员接口:仅ADMIN角色可访问
                .requestMatchers("/user/**").hasAnyRole("ADMIN", "USER")  // 用户接口:ADMIN/USER可访问
                .anyRequest().authenticated()  // 其他所有接口:必须登录才能访问
            )

            // ② 配置登录行为
            .formLogin(form -> form
                .loginPage("/login")  // 自定义登录页URL(默认使用Spring Security自带登录页)
                .loginProcessingUrl("/do-login")  // 登录提交接口(Spring Security自动处理验证)
                .defaultSuccessUrl("/home", true)  // 登录成功后跳转的页面
                .failureUrl("/login?error")  // 登录失败后跳转的页面
                .permitAll()  // 允许所有人访问登录页
            )

            // ③ 配置退出登录行为
            .logout(logout -> logout
                .logoutUrl("/logout")  // 退出接口
                .logoutSuccessUrl("/login?logout")  // 退出成功后跳转的页面
                .invalidateHttpSession(true)  // 清除会话(默认true)

            // ④ 关闭CSRF防护(前后端分离项目必关,否则POST请求会被拦截)
            .csrf(csrf -> csrf.disable())  // 单体项目建议开启,通过表单提交CSRF令牌

            // 关联认证提供者
            .authenticationProvider(authenticationProvider());

        return http.build();
    }
}

学习 Spring Security 可以按照 “核心概念→基础配置→实战场景→源码理解” 的路径循序渐进,
它是 Spring 生态中负责身份认证(Authentication) 和授权(Authorization) 的安全框架,广泛用于保护接口、实现登录验证、权限控制等场景。

一、先搞懂 2 个核心问题(避免从一开始就陷入细节)
Spring Security 解决什么问题?
认证(Who are you?):验证用户身份(比如登录时校验用户名密码是否正确)。
授权(What are you allowed to do?):验证用户是否有权限访问某个资源(比如普通用户不能访问管理员接口)。
它和 Shiro 的区别?
Spring Security 与 Spring 生态无缝集成(如 Spring Boot、Spring Cloud),功能更全(默认支持 OAuth2、JWT 等),但学习曲线稍陡;
Shiro 更轻量、易上手,适合中小项目。
实际开发中,Spring Boot 项目优先选 Spring Security。
二、学习路径:从基础到实战(附关键代码示例)
阶段 1:环境搭建与入门案例(1-2 天)
目标:跑通第一个 Spring Security 程序,理解默认行为。

引入依赖(Spring Boot 项目)
在 pom.xml 中添加:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

(引入后,项目所有接口会被默认保护,访问需登录)
第一个案例:默认登录流程
启动项目后访问任意接口(如 http://localhost:8080/hello),会自动跳转到 Spring Security 提供的默认登录页(用户名默认 user,密码在控制台打印,格式:Using generated security password: xxxxx)。
登录成功后才能访问接口 —— 这就是 Spring Security 的 “默认保护” 机制。
自定义内存用户(覆盖默认用户)
创建配置类,定义用户、角色、密码:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // 定义用户(内存模式,仅用于测试)
    @Bean
    public UserDetailsService userDetailsService() {
        // 角色:ROLE_ADMIN、ROLE_USER(Spring Security 角色默认前缀 ROLE_)
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder().encode("123456")) // 密码必须加密
                .roles("ADMIN") // 等价于权限 ROLE_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(); // 推荐的加密算法
    }

    // 配置安全规则(哪些接口需要什么权限)
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll() // /public 开头的接口允许匿名访问
                .requestMatchers("/admin/**").hasRole("ADMIN") // /admin 接口需要 ADMIN 角色
                .anyRequest().authenticated() // 其他接口需要登录
            )
            .formLogin(); // 启用默认表单登录

        return http.build();
    }
}

测试:
/public/hello:无需登录可访问;
/admin/hello:仅 admin 用户(密码 123456)可访问;
其他接口(如 /user/hello):user 或 admin 登录后可访问。

阶段 2:核心概念与组件(2-3 天)

目标:理解 Spring Security 的工作原理,避免 “只会配不会用”。

核心组件(必须掌握)
SecurityContextHolder:存储当前登录用户的安全上下文(包含认证信息),可在任意地方获取当前用户:

java
运行
// 获取当前登录用户名
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();

Authentication:表示 “认证信息”,包含用户名、密码、权限集合等。
UserDetailsService:用于加载用户信息(如从数据库查询用户),是 “认证” 的核心接口(自定义认证逻辑必实现)。
PasswordEncoder:密码加密器,用于加密存储密码和验证密码(如 BCrypt)。
SecurityFilterChain:配置安全规则(哪些接口需要权限、登录方式等)。
认证流程简化版
用户提交用户名密码 →

  1. Spring Security 封装为 UsernamePasswordAuthenticationToken →
  2. 调用 UserDetailsService 获取用户信息(UserDetails)→
  3. 用 PasswordEncoder 比对密码 →
  4. 认证成功后,将 Authentication 存入 SecurityContextHolder。

阶段 3:实战场景(3-5 天)

目标:掌握项目中常用的功能,如数据库认证、自定义登录、权限控制等。

数据库认证(替代内存用户)
实际项目中用户信息存在数据库,需自定义 UserDetailsService 从数据库查询用户:
步骤 1:创建用户表(至少包含 username、password、role 字段);
步骤 2:实现 UserDetailsService:

java
运行
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper; // 假设用 MyBatis/JPA 操作数据库

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

        // 2. 转换为 Spring Security 所需的 UserDetails(包含权限)
        // 角色需要加 ROLE_ 前缀,或在数据库直接存 ROLE_ADMIN
        Collection<? extends GrantedAuthority> authorities = 
            AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_" + user.getRole());

        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(), // 数据库存储的是加密后的密码
            authorities
        );
    }
}

步骤 3:SecurityConfig 中无需再定义 userDetailsService Bean(Spring 会自动扫描自定义的实现类)。
自定义登录页面与逻辑
默认登录页太简陋,实际项目需自定义:

java
运行
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/login.html", "/login").permitAll() // 登录页和登录接口允许匿名访问
            .anyRequest().authenticated()
        )
        .formLogin(form -> form
            .loginPage("/login.html") // 自定义登录页路径
            .loginProcessingUrl("/login") // 登录请求提交的接口(Spring Security 自动处理)
            .usernameParameter("username") // 表单中用户名的参数名(默认就是 username)
            .passwordParameter("password") // 表单中密码的参数名(默认就是 password)
            .defaultSuccessUrl("/index", true) // 登录成功后跳转的页面
            .failureUrl("/login.html?error") // 登录失败后跳转的页面
        );

    return http.build();
}

基于注解的授权(方法级别权限控制)
在 Controller 方法上直接指定权限要求,更灵活:
步骤 1:在启动类或配置类加 @EnableMethodSecurity 开启注解支持;
步骤 2:在方法上使用注解:

java
运行
@RestController
public class UserController {
    // 必须有 ADMIN 角色才能访问(等价于 hasRole("ADMIN"))
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/admin/hello")
    public String adminHello() {
        return "admin hello";
    }

    // 必须有 USER 角色或 ADMIN 角色才能访问
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    @GetMapping("/user/hello")
    public String userHello() {
        return "user hello";
    }
}

前后端分离场景:JWT 认证
前后端分离项目常用 JWT(JSON Web Token)代替 Session,流程:
用户登录 → 后端验证成功后生成 JWT 令牌 → 返回给前端;
前端后续请求在 Header 中携带 JWT(Authorization: Bearer xxx);
后端通过过滤器验证 JWT 有效性,解析用户信息。
核心代码(简化版过滤器):

java
运行
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 1. 从 Header 中获取 JWT
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
        }

        // 2. 验证 JWT 并解析用户信息
        if (tokenProvider.validateToken(token)) {
            String username = tokenProvider.getUsernameFromToken(token);
            // 3. 将用户信息存入 SecurityContext
            Authentication auth = new UsernamePasswordAuthenticationToken(username, null, getAuthorities(username));
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

(需在 SecurityConfig 中配置过滤器生效)
阶段 4:进阶与源码(按需深入)
目标:解决复杂场景,理解框架底层设计。

常见进阶场景
退出登录:配置 logout() 实现 Session 清除或 JWT 失效;
记住我功能:通过 rememberMe() 配置,实现 “下次自动登录”;
验证码:在登录流程中添加验证码校验(需自定义过滤器);
OAuth2.0 第三方登录:集成微信、GitHub 等第三方登录(Spring Security OAuth2 模块)。
源码阅读重点
过滤器链(FilterChainProxy):Spring Security 的核心是一系列过滤器,按顺序执行认证、授权等逻辑;
UsernamePasswordAuthenticationFilter:处理表单登录的核心过滤器;
AbstractSecurityInterceptor:处理授权逻辑的抽象类。
三、推荐学习资源
官方文档:Spring Security 官方指南(最权威,建议结合示例代码看);
视频教程:
B 站 “狂神说 Spring Security”(入门快速上手);
尚硅谷 “Spring Security 实战”(深入源码与实战);
书籍:《Spring Security 实战》(韩露著,适合初学者)、《Spring Security in Action》(国外经典);
实战项目:自己搭建一个 “权限管理系统”,实现用户管理、角色分配、接口权限控制全流程。
四、避坑指南
密码必须加密:Spring Security 5+ 强制要求配置 PasswordEncoder,否则启动报错;
角色 vs 权限:角色(ROLE_XXX)是一种特殊的权限,hasRole(“ADMIN”) 等价于 hasAuthority(“ROLE_ADMIN”);
配置顺序:authorizeHttpRequests 中规则顺序很重要,更具体的规则(如 /admin/**)要放在前面,否则会被后面的通用规则(如 anyRequest())覆盖;
前后端分离跨域:需在 SecurityConfig 中配置 cors() 允许跨域,否则登录请求会被拦截。

按照这个路径学习,1-2 周可掌握基础用法,1 个月左右可应对大部分实战场景。关键是多写代码,遇到问题调试源码(比如登录失败时,跟踪 UserDetailsService 是否正确返回用户信息),理解 “认证→授权” 的核心流程后,很多配置会变得很自然。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!