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>'
相关推荐
安当加密4 小时前
Nacos配置安全治理:把数据库密码从YAML里请出去
数据库·安全
jenchoi4134 小时前
【2025-11-02】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
安全·web安全·网络安全
上海云盾-高防顾问4 小时前
什么是端口管理?网络安全的关键环节
安全·web安全
GOATLong4 小时前
git使用
大数据·c语言·c++·git·elasticsearch
Black蜡笔小新6 小时前
视频融合平台EasyCVR结合视频智能分析技术构建高空抛物智能监控系统,守护“头顶上的安全”
安全·音视频
风语者日志6 小时前
[LitCTF 2023]作业管理系统
前端·网络·安全·web安全·ctf
EasyGBS7 小时前
从“被动监控”到“主动预警”:EasyGBS远程视频监控方案助力企业高效安全运营
安全·音视频
uhakadotcom7 小时前
基于 TOON + Next.js 来大幅节省 token 并运行大模型
前端·面试·github
孟陬8 小时前
别再社死了!`includeIf` 一招搞定 Git 提交者信息错乱,守护你的邮箱隐私
git·github