首页 / Spring Boot 入门教程 / Spring Security 入门

Spring Boot 入门教程

Spring Security 入门

本教程共 48 篇 · 第 33 篇 · 更新于 2026-08-13 · 约 5 分钟阅读

Spring BootSpring Security认证授权SecurityFilterChainBCrypt方法级安全登录表单

本节目标:理解 Spring Security 的默认安全行为,会配置内存用户、编写 SecurityFilterChain 访问规则,掌握方法级安全注解。

认证与授权:安全的两件事

安全问题可以拆成两个词:认证(Authentication)和授权(Authorization)。

认证回答「你是谁」。进门先验身份证,验证通过你就登录了。

授权回答「你能干什么」。同样是登录用户,普通员工进不了机房,只有管理员能改配置。

Spring Security 把这两件事都管了。它是 Java 世界事实上的安全标准,Spring Boot 官方集成,配置好了几乎不用写业务安全代码。

不加一行代码,应用就被保护了

在 Spring Boot 里接入它出奇地简单:加一个依赖,重启应用,所有接口立刻要求登录。

Spring Boot 4.1.0 基于 Spring Security 7。默认行为是:

  • 所有请求都要认证,/error 页面也不例外
  • 内置一个用户,用户名 user,密码随机生成
  • 密码在启动日志里以 WARN 级别打印
  • 根据请求头自动选择表单登录或 HTTP Basic 认证

背后是两段自动配置在干活:SecurityAutoConfiguration 负责装过滤器链,UserDetailsServiceAutoConfiguration 负责造默认用户。

随机密码只给开发用。生产环境必须换成自己的安全配置。

引入依赖

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

加了依赖,什么代码都不用写。mvn spring-boot:run 启动,日志里找这一行:

Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

浏览器访问 http://localhost:8080/,会跳到登录页。输入 user 和这串密码,就能进去了。

Tip

用 curl 测试更直接:curl -u user:78fa095d-3f4c-48b1-ad50-e24c31d5cf35 http://localhost:8080/
请求头带 Accept: application/json 时,Spring Security 会改用 HTTP Basic 认证,不弹表单。

Warning

日志里看不到密码?检查日志配置有没有把 org.springframework.boot.security.autoconfigure 这个分类压到 WARN 以下。默认密码打印依赖它。

固定用户名密码

随机密码每次启动都变,调试麻烦。可以在配置文件里固定:

spring:
  security:
    user:
      name: admin
      password: "123456"
Warning

明文密码只适合本地体验。spring.security.user.* 写在配置文件里,会跟着代码走,千万别用于生产。

让用户可控:内存用户

默认用户只有一个。想定义多个用户、分配角色,写一个 UserDetailsService Bean。

UserDetailsService 是 Spring Security 的「用户档案库」接口,只负责一件事:按用户名查出用户。查内存、查数据库、查 LDAP,都实现它。

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class UserConfig {

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails user = User.withUsername("zhangsan")
                .password(encoder.encode("123456"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("admin123"))
                .roles("ADMIN", "USER")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

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

要点:

  • roles("ADMIN") 等价于给一个 ROLE_ADMIN 权限
  • 密码必须加密后存储,BCryptPasswordEncoder 是默认推荐
  • 定义了 UserDetailsService Bean 后,Boot 的默认用户自动退出
Note

老教程里常见 {noop}123456 前缀,意思是「不加密」。它只用来演示,Spring Security 7 里强烈不建议。
用户存数据库时,把 InMemoryUserDetailsManager 换成 JdbcUserDetailsManager 即可,登录逻辑不变。

自定义访问规则:SecurityFilterChain

默认规则是「全部要登录」。放开某些路径、按角色限流,要写 SecurityFilterChain Bean。这是 Spring Security 5.7 之后的现代写法。

package com.example.demo.config;

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.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/home", "/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults())
            .httpBasic(withDefaults())
            .logout(logout -> logout.logoutSuccessUrl("/login?logout"));
        return http.build();
    }
}

规则从上往下匹配,先命中先生效。permitAll() 放行,hasRole("ADMIN") 要求 ADMIN 角色,authenticated() 只要登录即可。

formLogin 负责表单登录:默认登录页在 /login,表单字段名是 usernamepassword,提交到 /loginlogout 处理退出,默认监听 /logout 的 POST 请求。

Warning

网上大量教程还在用 WebSecurityConfigurerAdapter 继承写法。它在 5.7 弃用、6.0 移除,Spring Security 7 里已经不存在,遇到直接跳过。

Note

定义了 SecurityFilterChain Bean 后,Spring Boot 的默认安全配置自动退出,规则完全由你掌控。
@EnableWebSecurity 在 Spring Boot 里其实可以省略,Boot 会自动装配;写上更明确,也方便脱离 Boot 时理解。

方法级安全

URL 规则管「能不能访问这个路径」。方法级安全管「能不能调用这个方法」,适合精细控制。

典型场景:删除用户这个操作,只有 ADMIN 角色能调,但查看列表 USER 也能看。路径都写在 /api/users 下,用 URL 规则拆就很别扭。

三步开启:

  1. 配置类上加 @EnableMethodSecurity
  2. 在方法上加 @PreAuthorize("hasRole('ADMIN')")
  3. 权限不足时,调用直接抛 AccessDeniedException
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    @PreAuthorize("hasRole('ADMIN')")
    public String listUsers() {
        return "只有管理员能看";
    }

    @PostMapping
    @PreAuthorize("hasAnyRole('ADMIN', 'USER')")
    public String createUser() {
        return "登录用户都能操作";
    }
}

@Secured("ROLE_ADMIN")@RolesAllowed("ADMIN") 也支持,要在 @EnableMethodSecurity 里打开对应开关:

@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
}

三种注解选一个用就行。新手建议从 @PreAuthorize 开始,它支持 SpEL 表达式,最灵活。

Note

Spring Security 6.0 起,@EnableGlobalMethodSecurity 改名为 @EnableMethodSecurity,参数用法不变。

登录流程串一遍

把上面的概念串成完整流程:

  1. 浏览器访问受保护页面,未登录,被重定向到 /login
  2. 用户提交用户名密码,POST 到 /login
  3. 表单里自动带 CSRF token,Spring Security 校验通过
  4. UserDetailsService 查出用户,PasswordEncoder 比对密码
  5. 认证成功,建立 Session,跳回原页面
  6. 之后每个请求带 Cookie,安检直接放行

退出走 POST /logout,默认清 Session 并跳转。logoutSuccessUrl 指定退出后去哪。

每一步都能换成自己的实现:用户从数据库来,就换 UserDetailsService;密码换算法,就换 PasswordEncoder。Spring Security 的设计就是「可替换的安检设备」。

初学者常栽的坑

  • 密码存了明文:数据库泄露等于用户全裸奔,一律 BCrypt 加密
  • 表单登录 302 跳来跳去:多半是 CSRF 校验失败或登录失败循环,先看日志里的认证异常
  • {noop} 图省事:只配出现在本地演示,别带上生产
  • 规则顺序写反:anyRequest().authenticated() 写在 permitAll() 前面,后面的放行全部失效
  • 自定义了 SecurityFilterChain 却以为默认保护还在:Boot 的默认配置会退出,漏配的路径可能裸奔
  • 测试登录失败:注意表单登录和 Basic 认证是两套流程,curl 不带 JSON Accept 头会拿到 302 跳转
  • 角色名写错大小写:hasRole("admin") 和数据库里的 ROLE_ADMIN 对不上,授权永远失败

本节小结

  • spring-boot-starter-security 依赖,全站立即要求认证
  • 默认用户 user,随机密码看启动日志
  • 多用户用 InMemoryUserDetailsManager,密码用 BCrypt 加密
  • 访问规则写 SecurityFilterChain Bean,WebSecurityConfigurerAdapter 已移除
  • 精细控制用 @EnableMethodSecurity + @PreAuthorize

下一节讲 REST API 场景:无状态的 JWT 认证和 OAuth2。