OAuth 2.0 安全授权

一、什么是 OAuth 2.0?

OAuth 2.0 是一个授权框架,它允许第三方应用程序在获得用户授权后,代表用户访问该用户在另一个服务提供商上存储的资源,而无需分享用户的密码。

  • 重要区别:OAuth 2.0 是关于授权,而非认证。
    • 认证:是确认"你是谁"的过程。(例如:用账号密码登录)
    • 授权:是确认"你被允许做什么"的过程。(例如:授权一个App来管理你的QQ邮箱)

二、为什么需要 OAuth 2.0?

在没有 OAuth 的时代,如果你想用一个第三方应用(比如一个在线邮件管理工具)来管理你的多个邮箱,你不得不把邮箱的账号密码交给这个第三方应用。这会带来巨大的风险:

  1. 密码泄露:第三方应用可能不安全,或者会滥用你的密码。
  2. 权限过大:应用不仅获得了读取邮件的权限,还可能获得删除邮件、发送邮件等所有权限。
  3. 难以撤销:一旦密码给出,除非修改密码,否则无法单独撤销该应用的访问权。

OAuth 2.0 通过引入一个访问令牌 完美地解决了这些问题。

三、OAuth 2.0 的核心角色

在一个 OAuth 2.0 流程中,通常涉及四个角色:

  1. 资源所有者: 拥有受保护资源的用户(就是你)。
  2. 客户端: 想要访问用户资源的第三方应用程序(例如,一个想访问你微信好友列表的社交App)。
  3. 授权服务器: 服务提供商用来处理认证和授权的服务器。它负责在用户授权后,向客户端颁发访问令牌。
  4. 资源服务器: 存放用户受保护资源的API服务器。客户端使用访问令牌来访问资源服务器。

四、OAuth2.0 四种模式

基于不同的使用场景,OAuth2.0设计了四种模式来获取访问令牌。Spring Security OAuth2提供了内置的端点,四种授权模式分别使用不同的端点和参数,我们在客户端配置中指定允许的授权类型,并在端点配置中设置必要的组件(如authenticationManager)来支持密码模式。

  • 授权端点(/oauth/authorize):用于处理授权请求。
  • 令牌端点(/oauth/token):用于颁发访问令牌。
  • 令牌验证端点(/oauth/check_token):用于验证令牌有效性。
  • 令牌吊销端点(/oauth/revoke_token):用于吊销令牌。
  • JWK端点( /oauth/token_key):获取JWT公钥。
4.1 授权码模式

这是最常用、最安全的模式,特别是用于有后端服务器的 Web 应用程序和移动应用程序。

  • 浏览器访问示例:

    • response_type=code
    bash 复制代码
    http://localhost:8080/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://localhost:8081/callback&scope=all
  • 流程:

    • 第一步:用户访问授权端点
      • 客户端将用户重定向到/oauth/authorize,参数包括response_type=code、client_id、redirect_uri、scope等。
    • 第二步:用户认证并授权
      • 用户登录并授权,授权服务器重定向到redirect_uri并带上授权码。
    • 第三步:用授权码交换访问令牌
      • 客户端使用授权码向/oauth/token请求访问令牌,参数包括grant_type=authorization_code、code、redirect_uri等,并且需要客户端认证(通过HTTP Basic认证或参数传递client_id和client_secret)。

      • 换取访问令牌的请求:

        bash 复制代码
        curl -X POST \
          http://localhost:8080/oauth/token \
          -H 'Authorization: Basic Y2xpZW50MToxMjM0NTY=' \  # client1:123456的Base64编码
          -H 'Content-Type: application/x-www-form-urlencoded' \
          -d 'grant_type=authorization_code&code=授权码&redirect_uri=http://localhost:8081/callback'
  • 特点:

    • 安全性高:关键的访问令牌不会通过浏览器暴露,只有授权码在前端传递,而授权码本身是短暂的且无法直接访问资源。
    • 支持刷新令牌:可以获取刷新令牌,用于在访问令牌过期后获取新的访问令牌,无需用户再次登录。
    • 适用场景:有后端的传统 Web 应用、移动 App(使用 PKCE 扩展增强安全)。
4.2 隐式模式

这是一种简化模式,访问令牌直接在浏览器的重定向 URI 的片段(#后面)中返回。该模式在现代 OAuth 2.1 标准中已被废弃,不推荐使用。

  • 浏览器访问示例:

    • response_type=token
    bash 复制代码
    http://localhost:8080/oauth/authorize?response_type=token&client_id=client1&redirect_uri=http://localhost:8081/callback&scope=all
  • 流程

    • 第一步:用户访问授权端点
      • 客户端将用户重定向到/oauth/authorize,参数包括response_type=token、client_id、redirect_uri、scope等。
    • 第二步:用户认证并授权
      • 用户登录并授权,授权服务器重定向到redirect_uri并在URL的hash片段中直接返回访问令牌。
  • 特点:

    • 安全性低:访问令牌直接在浏览器地址栏中暴露,有被截获的风险。
    • 不支持刷新令牌:由于安全性考虑,不会返回刷新令牌。
    • 适用场景:过去用于纯前端 JavaScript 应用(单页应用 SPA),但现在已被授权码模式 + PKCE 完全取代。
4.3 密码模式

用户将其用户名和密码直接交给客户端应用,客户端应用再用这些信息向授权服务器申请令牌。

  • 浏览器访问示例:

    • grant_type=password
    bash 复制代码
    curl -X POST \
      http://localhost:8080/oauth/token \
      -H 'Authorization: Basic Y2xpZW50MToxMjM0NTY=' \
      -H 'Content-Type: application/x-www-form-urlencoded' \
      -d 'grant_type=password&username=user&password=123456&scope=all'
  • 流程:

    • 用户直接访问令牌端点
      • 客户端直接向/oauth/token发送请求,参数包括grant_type=password、username、password、scope,并且需要客户端认证。
  • 特点:

    • 信任度要求极高:客户端应用会直接接触到用户的明文密码,这违背了 OAuth 的初衷(不暴露密码)。只在绝对信任的情况下使用,例如同一个公司内部的官方应用。
    • 安全性风险:如果客户端应用被入侵,用户密码将泄露。
    • 适用场景:第一方的高信任度应用,或者遗留系统迁移到 OAuth 的过渡方案。
4.4 客户端凭证模式

这种模式不涉及用户,是机器对机器的通信,用于客户端应用访问其自身拥有的资源,而不是代表某个用户。

  • 浏览器访问示例:

    • grant_type=client_credentials
    bash 复制代码
    curl -X POST \
      http://localhost:8080/oauth/token \
      -H 'Authorization: Basic Y2xpZW50MToxMjM0NTY=' \
      -H 'Content-Type: application/x-www-form-urlencoded' \
      -d 'grant_type=client_credentials&scope=all'
  • 流程:

    • 用户直接访问令牌端点
      • 客户端向/oauth/token发送请求,参数包括grant_type=client_credentials、scope,并且需要客户端认证。
  • 特点:

    • 无用户参与:整个流程没有用户授权环节。
    • 访问自有资源:获取的令牌用于访问该客户端自己控制的资源(例如,一个后台服务调用另一个 API 来获取系统级别的数据)。
    • 适用场景:微服务之间的 API 调用、后台作业、服务器与服务器之间的通信。

五、搭建 OAuth2-Server

这里使用Spring Boot 2.x 和 Spring Security OAuth2 来搭建一个 OAuth2 Server 认证中心。主要步骤:

  1. 创建Spring Boot项目,添加依赖。
  2. 配置授权服务器。
  3. 配置资源服务器(如果需要保护资源)。
  4. 配置安全规则。
  5. 测试授权并访问保护资源。
步骤1:创建Spring Boot项目

使用Spring Initializr 创建一个项目,并在pom.xml中添加依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
</dependencies>
步骤2:配置授权服务器

创建一个授权服务器配置类,使用@EnableAuthorizationServer注解。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")   // 公开/oauth/token_key端点
                .checkTokenAccess("isAuthenticated()") // 认证后可访问/oauth/check_token端点
                .allowFormAuthenticationForClients(); // 允许客户端使用表单认证
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client1") // 客户端ID
                .secret(passwordEncoder.encode("123456")) // 客户端密钥
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit") // 授权模式
                .scopes("all") // 授权范围
                .redirectUris("http://localhost:8080/callback") // 重定向URI,用于授权码模式和简化模式
                .autoApprove(true) // 自动批准,跳过授权确认页面
                .and()
                .withClient("client2")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit")
                .scopes("all")
                .redirectUris("http://localhost:8080/callback")
                .autoApprove(true);
    }
}
步骤3:配置资源服务器

创建一个资源服务器配置类,使用@EnableResourceServer注解。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/public/**").permitAll() // 公开访问
                .anyRequest().authenticated(); // 其他请求需要认证
    }
}
步骤4:配置安全规则

我们需要配置一个密码编码器,并设置基本的安全规则。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password(passwordEncoder().encode("123456"))
                .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/oauth/**").permitAll() // OAuth2端点允许访问
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();
    }
}
步骤5:测试授权并访问保护资源
  1. 创建一个测试控制器

    java 复制代码
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestController {
    
        @GetMapping("/public/hello")
        public String publicHello() {
            return "Hello Public";
        }
    
        @GetMapping("/private/hello")
        public String privateHello() {
            return "Hello Private";
        }
    }
  2. 获取访问令牌

    这里使用授权码模式来获取访问令牌:

    1. 在浏览器中访问:

      bash 复制代码
      http://localhost:8080/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://localhost:8081/callback&scope=read
    2. 输入用户名和密码(user1/password1)进行登录。

    3. 授权服务器会重定向到http://localhost:8081/callback?code=授权码。

    4. 使用这个授权码,通过POST请求到/oauth/token获取访问令牌。

  3. 使用获取到的访问令牌访问受保护资源:

    bash 复制代码
    curl -X GET \
      http://localhost:8080/private/hello \
      -H 'Authorization: Bearer <access_token>'
相关推荐
Selicens1 小时前
git批量删除本地多余分支
前端·git·后端
崔庆才丨静觅6 小时前
Claude Code GitHub Actions 使用教程
github·api·claude
用户9623779544821 小时前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机1 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机1 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
闲云一鹤1 天前
Git LFS 扫盲教程 - 你不会还在用 Git 管理大文件吧?
前端·git·前端工程化
用户962377954481 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star1 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
砖厂小工1 天前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github