5分钟搞懂登录认证和授权框架Spring Security

文章来源声明: 原文作者:33780; 来源站点:掘金; 原文链接:https://juejin.cn/post/7701257148911747123; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

以保安类比降低理解门槛,适合Spring Security入门者快速建立认证授权心智模型,也可作为培训或文档导读素材。

🏢 把 Spring Security 想象成一栋大楼的保安系统 --------------------------------

你的网站就是一栋大楼,里面有很多房间(页面/接口)。Spring Security 就是门口的保安。

保安只干两件事:

  1. 认证:你是谁?→ 出示工牌(登录)
  2. 授权:你能进哪个房间?→ 看你的工牌等级(角色权限)

就这么简单。下面一步步来。

第一步:不装保安 vs 装保安

不装保安(不加依赖):谁都能进任何房间,随便逛。

装了保安(加 spring-boot-starter-security 依赖):你一启动项目,保安就自动上岗了。这时候你访问任何页面,都会被拦下来问:"你谁啊?先登录!"

保安会给你一个默认登录页。用户名固定是 user,密码在启动日志里打印出来(一串乱码)。你输对了才能进。

这一步你一行代码都不用写,保安就自动工作了。这就是 Spring Security 的默认行为。

第二步:告诉保安"有哪些人"

默认只有一个 user,肯定不够。你要给保安一份花名册,告诉他:

  • 张三,密码 123456,普通员工
  • 李四,密码 888888,管理员

这份花名册,在代码里就叫 UserDetailsService。你写一个方法,返回"张三、李四"这些人,保安就知道了。

关键词:花名册 = UserDetailsService。你只要记住"我要给保安一份名单"就行。

第三步:告诉保安"哪个房间谁能进"

保安拿到花名册后,还得知道规则。你要写一份门禁规则:

  • /public/ 开头的房间 → 谁都能进,不用出示工牌
  • /admin/ 开头的房间 → 只有管理员能进
  • 其他房间 → 登录了就能进

这份规则写在 SecurityFilterChain 里。你只要记住:这里就是写"哪个门谁能进"的地方。

代码长这样(不用背,看个意思):

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/public/**").permitAll()      // 公共房间,随便进
    .requestMatchers("/admin/**").hasRole("ADMIN")  // 管理员房间,查工牌
    .anyRequest().authenticated()                   // 其他房间,登录就行
);

翻译成人话就是:公共的随便进,管理员的要管理员,剩下的登录了就能进。

第四步:密码为什么要"加密"

花名册里存密码,不能存明文(比如直接存 123456),万一花名册被偷了,全完蛋。

所以保安要求:存进去之前,先把密码"搅碎"(加密)。等你登录时,他把你输入的密码也用同样的方法搅碎,比对两个碎渣是否一样。

这个"搅碎机"在代码里叫 PasswordEncoder,最常用的是 BCryptPasswordEncoder。

记住:密码必须搅碎存,不能存明文。

🎯 整个流程串起来(重点看这个)

你访问 /admin/页面
        ↓
保安拦住:出示工牌!(认证)
        ↓
你输入 用户名+密码
        ↓
保安查花名册(UserDetailsService):有这个人吗?密码搅碎后对得上吗?
        ↓
对上了 → 发你一张通行证(SecurityContext 里存下你的信息)
        ↓
保安再看门禁规则(SecurityFilterChain):/admin/ 要 ADMIN 角色
        ↓
查你工牌:你是 ADMIN 吗?
   ├─ 是 → 放行,进房间 ✅
   └─ 否 → 拦住,403 没权限 ❌

你只需要记住 3 个词

词大白话作用
**UserDetailsService**花名册告诉保安有哪些人、密码、角色
**SecurityFilterChain**门禁规则告诉保安哪个房间谁能进
**PasswordEncoder**搅碎机把密码加密,不明文存

认证 = 查你是谁(登录)
授权 = 查你能不能进(角色权限)

那你实际要写几段代码?

  1. 一个类,里面放花名册(UserDetailsService)
  2. 同一个类里,放门禁规则(SecurityFilterChain)
  3. 再配一个搅碎机(PasswordEncoder)

就这三样。其他都是细节。

具体怎么实现

下面是一份完整能跑的代码,照着做,跑起来就能看到效果。

📁 项目结构

src/main/java/com/example/demo/
├── DemoApplication.java          (启动类,Spring Boot 自带,不用改)
├── SecurityConfig.java           (★ 新写:花名册 + 门禁规则 + 搅碎机)
└── HelloController.java          (★ 新写:几个测试页面)

1️⃣ 依赖(pom.xml)

只要加这一个,其他都是 Spring Boot 自带的:

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

2️⃣ SecurityConfig.java(核心,花名册 + 门禁 + 搅碎机)

package com.example.demo;

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.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;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // ========== 搅碎机:密码加密用 ==========
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // ========== 花名册:告诉保安有张三、李四 ==========
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        // 张三:普通员工,角色 USER
        UserDetails zhangsan = User.withUsername("zhangsan")
        .password(encoder.encode("123456"))  // 密码搅碎后存
        .roles("USER")
        .build();

        // 李四:管理员,角色 ADMIN
        UserDetails lisi = User.withUsername("lisi")
        .password(encoder.encode("888888"))
        .roles("ADMIN")
        .build();

        return new InMemoryUserDetailsManager(zhangsan, lisi);
    }

    // ========== 门禁规则:哪个房间谁能进 ==========
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()      // 公共房间,谁都能进
            .requestMatchers("/admin/**").hasRole("ADMIN")  // 管理员房间,只有李四能进
            .requestMatchers("/user/**").hasRole("USER")    // 员工房间,只有张三能进
            .anyRequest().authenticated()                   // 其他房间,登录了就能进
                              )
        .formLogin(form -> form
                   .permitAll()   // 用默认登录页,登录页本身不用登录就能看
                  )
        .logout(logout -> logout.permitAll());  // 允许退出登录
        return http.build();
    }
}

3️⃣ HelloController.java(测试房间)

package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    // 公共房间:不用登录就能进
    @GetMapping("/public/hello")
    public String publicHello() {
        return "这是公共页面,谁都能看!";
    }
    // 员工房间:只有张三(USER)能进
    @GetMapping("/user/hello")
    public String userHello() {
        return "这是员工页面,张三你好!";
    }
    // 管理员房间:只有李四(ADMIN)能进
    @GetMapping("/admin/hello")
    public String adminHello() {
        return "这是管理员页面,李四你好!";
    }
    // 其他房间:登录了就能进(张三李四都行)
    @GetMapping("/any")
    public String anyPage() {
        return "你登录了,所以能进这个页面。";
    }
}

▶️ 怎么测试(照着点就行)

启动项目,然后在浏览器里依次访问:

① 访问 /public/hello
→ 直接显示"这是公共页面",不用登录 ✅

② 访问 /any
→ 自动跳到登录页。输入 zhangsan / 123456
→ 显示"你登录了",成功 ✅

③ 登录张三后,访问 /admin/hello
→ 显示 403 没权限(因为张三是 USER,不是 ADMIN)❌
→ 这就是"授权"在起作用

④ 退出,换李四登录( lisi / 888888 ),访问 /admin/hello
→ 显示"这是管理员页面",成功 ✅

⑤ 李四访问 /user/hello
→ 403,因为李四不是 USER ❌

🎬 你会看到的现象

  • 没登录时访问受保护页面 → 自动跳登录页(认证)
  • 登录了但角色不对 → 403 拒绝(授权)
  • 公共页面 → 直接放行(permitAll)

💡 对照"保安"理解

代码保安里的角色
`userDetailsService()`花名册:张三、李四两人
`passwordEncoder()`搅碎机:密码加密
`filterChain()`里的 `requestMatchers`门禁规则:哪个房间谁能进
登录页输密码认证:查你是谁
403 拒绝授权:查你能不能进

跑完这 5 步,你就把 Spring Security 的认证和授权都亲眼见过了。之后再学数据库查用户、JWT 令牌,都是在这套骨架上换零件而已。