下载工作台
Spring Boot Web 开发

Spring Security 认证与授权

试读上半部分 · 解锁后可读全文

第 6 章 · Spring Security 认证与授权

本章目标:集成 Spring Security 6;实现基于 UserDetailsService 的用户加载与 BCrypt 密码编码;配置 表单登录、登出与 URL 访问规则;在 HTML 表单中正确使用 CSRF Token(承接 ch04);使用 @PreAuthorize 方法级权限;对照 shop-demo(Django)auth、Session、权限组说明差异;保护 shop-spring-demo/catalog/** 管理页,开放 /health 与部分只读 API。

学时建议:6~7 小时(含 2.5 小时跟练)

前置:完成 spring-boot-web ch05django-web ch03 认证章节更佳;理解 HTTP Session 与 Cookie。


6.1 场景说明:运营后台必须登录

shop-spring-demo 的 Thymeleaf 商品管理页仅运营人员可访问;公开 GET /api/v1/products 可匿名(只读),写操作需认证。虚构管理域 https://admin.example.com 经表单登录演示。

路径访问策略shop-demo 对照
/health匿名健康检查
/api/v1/products GET匿名只读DRF AllowAny 只读
/api/v1/products POST认证 + 角色IsAuthenticated
/catalog/**认证 + ROLE_STAFF@login_required + staff
/h2-console/**仅 dev + 限制 IP无 Admin DB 控制台
说明:用户表、密码均为教学虚构;勿使用弱密码上任何真实环境。

6.2 依赖与默认行为

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

引入后默认

  • 所有端点需认证
  • 生成随机密码用户 user(控制台打印)——需自定义 SecurityFilterChain
  • 启用 CSRF(浏览器表单)
  • 提供 表单登录/login

启动后访问 /catalog 应跳转登录——说明 Security 已生效。


6.3 用户实体与 Repository

6.3.1 User 实体(简化)

@Entity
@Table(name = "users")
public class AppUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String username;

    @Column(nullable = false, length = 100)
    private String passwordHash;

    @Column(nullable = false)
    private boolean enabled = true;

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "role")
    private Set<String> roles = Set.of("ROLE_STAFF");

    protected AppUser() {}

    public AppUser(String username, String passwordHash) {
        this.username = username;
        this.passwordHash = passwordHash;
    }

    public String getUsername() { return username; }
    public String getPasswordHash() { return passwordHash; }
    public boolean isEnabled() { return enabled; }
    public Set<String> getRoles() { return roles; }
}

Flyway V3__users.sql(示例):

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(64) NOT NULL UNIQUE,
    password_hash VARCHAR(100) NOT NULL,
    enabled BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role VARCHAR(32) NOT NULL,
    PRIMARY KEY (user_id, role),
    FOREIGN KEY (user_id) REFERENCES users(id)
);
DjangoSpring
User 模型AppUser @Entity
is_staff / is_superuserROLE_STAFF / ROLE_ADMIN
Grouproles 集合或独立 Role 表(ch16 RBAC 深化)

6.3.2 Repository

public interface AppUserRepository extends JpaRepository<AppUser, Long> {
    Optional<AppUser> findByUsername(String username);
}

6.4 UserDetailsService 实现

@Service
public class DbUserDetailsService implements UserDetailsService {

    private final AppUserRepository userRepository;

    public DbUserDetailsService(AppUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AppUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

        var authorities = user.getRoles().stream()
                .map(SimpleGrantedAuthority::new)
                .toList();

        return User.builder()
                .username(user.getUsername())
                .password(user.getPasswordHash())
                .disabled(!user.isEnabled())
                .authorities(authorities)
                .build();
    }
}

Spring Security 的 User.builder() 是框架类,与实体 AppUser 区分。


6.5 BCrypt 密码编码

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

创建用户时:

String hash = passwordEncoder.encode("Demo@2026");  // 教学密码,勿用于生产
AppUser u = new AppUser("operator", hash);
userRepository.save(u);
算法DjangoSpring
默认PBKDF2 等BCrypt(可换 Argon2)
校验user.check_password()passwordEncoder.matches(raw, hash)
明文存储禁止禁止

切勿在日志或 API 中返回 passwordHash。初始化可用 CommandLineRunner 仅 dev profile 插入演示账号。


6.6 SecurityFilterChain 配置

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/health", "/css/**", "/login").permitAll()
                .requestMatchers("/h2-console/**").hasIpAddress("127.0.0.1")
                .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
                .requestMatchers("/catalog/**").hasRole("STAFF")
                .requestMatchers(HttpMethod.POST, "/api/v1/**").authenticated()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。