MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

掌握OAuth 2 0的安全框架

2024-06-275.3k 阅读

OAuth 2.0 基础概念

协议角色

OAuth 2.0 定义了四个主要的角色:资源拥有者(Resource Owner)、客户端(Client)、授权服务器(Authorization Server)和资源服务器(Resource Server)。

  1. 资源拥有者:能够授予对受保护资源访问权限的实体,通常是用户。例如,在一个社交媒体应用中,用户就是资源拥有者,他们拥有自己的照片、文章等资源。
  2. 客户端:请求访问受保护资源的应用程序。它需要获取资源拥有者的授权才能访问资源。比如一个第三方的图片编辑应用,它希望获取用户在社交媒体上的照片进行编辑,这个图片编辑应用就是客户端。
  3. 授权服务器:负责验证资源拥有者的身份,并向客户端颁发授权令牌。当用户在客户端请求授权时,授权服务器会对用户进行身份验证,若验证通过则发放授权令牌。以 Google 账户授权为例,Google 的授权服务器负责验证用户身份并发放令牌给请求授权的第三方应用。
  4. 资源服务器:托管受保护资源的服务器。它会验证接收到的令牌,并根据令牌的权限决定是否允许客户端访问资源。比如社交媒体的服务器存储着用户的照片,它就是资源服务器,只有在验证令牌合法后才会允许客户端获取照片。

授权流程

OAuth 2.0 有多种授权流程,常见的有授权码模式(Authorization Code Grant)、简化模式(Implicit Grant)、密码模式(Resource Owner Password Credentials Grant)和客户端凭证模式(Client Credentials Grant)。

  1. 授权码模式

    • 流程步骤
      • 客户端向授权服务器发起授权请求,请求中包含客户端标识、重定向 URI、授权类型(code)等信息。例如,第三方应用向授权服务器发送请求:https://authorization-server.com/authorize?client_id=my_client_id&redirect_uri=https://my-client.com/callback&response_type=code
      • 授权服务器验证请求并向资源拥有者展示授权页面,询问是否授权客户端访问其资源。
      • 资源拥有者同意授权后,授权服务器生成授权码,并将其重定向到客户端指定的重定向 URI 上。如:https://my-client.com/callback?code=AUTHORIZATION_CODE
      • 客户端使用授权码向授权服务器请求访问令牌。请求中需要包含授权码、客户端标识和客户端密钥等信息:POST https://authorization-server.com/token HTTP/1.1
      {
          "grant_type": "authorization_code",
          "code": "AUTHORIZATION_CODE",
          "redirect_uri": "https://my-client.com/callback",
          "client_id": "my_client_id",
          "client_secret": "my_client_secret"
      }
      
      • 授权服务器验证请求,若合法则发放访问令牌(Access Token)和刷新令牌(Refresh Token)。
    • 适用场景:适用于有后端服务器的客户端应用,因为客户端密钥可以安全地存储在后端服务器上,这种模式安全性较高。例如,一个 Web 应用使用第三方登录功能,就可以采用授权码模式。
  2. 简化模式

    • 流程步骤
      • 客户端向授权服务器发起授权请求,请求中包含客户端标识、重定向 URI、授权类型(token)等信息。例如:https://authorization-server.com/authorize?client_id=my_client_id&redirect_uri=https://my-client.com/callback&response_type=token
      • 授权服务器验证请求并向资源拥有者展示授权页面,询问是否授权客户端访问其资源。
      • 资源拥有者同意授权后,授权服务器直接在重定向 URI 的哈希部分返回访问令牌。如:https://my-client.com/callback#access_token=ACCESS_TOKEN&token_type=bearer&expires_in=3600
    • 适用场景:适用于没有后端服务器的客户端应用,如纯前端的 JavaScript 应用。但由于访问令牌直接暴露在 URL 中,安全性相对较低,只适用于安全性要求不高的场景。
  3. 密码模式

    • 流程步骤
      • 客户端直接向授权服务器发送包含资源拥有者用户名和密码的请求,请求访问令牌。请求示例:POST https://authorization-server.com/token HTTP/1.1
      {
          "grant_type": "password",
          "username": "user@example.com",
          "password": "user_password",
          "client_id": "my_client_id",
          "client_secret": "my_client_secret"
      }
      
      • 授权服务器验证用户名、密码以及客户端信息,若合法则发放访问令牌。
    • 适用场景:适用于高度信任的客户端,如企业内部应用。因为需要客户端直接获取用户的用户名和密码,风险较大,所以一般不用于面向公众的应用。
  4. 客户端凭证模式

    • 流程步骤
      • 客户端向授权服务器发送请求,请求中包含客户端标识和客户端密钥,请求访问令牌。请求示例:POST https://authorization-server.com/token HTTP/1.1
      {
          "grant_type": "client_credentials",
          "client_id": "my_client_id",
          "client_secret": "my_client_secret"
      }
      
      • 授权服务器验证客户端信息,若合法则发放访问令牌。
    • 适用场景:适用于客户端需要访问自己的资源,而不是代表资源拥有者访问资源的情况。例如,一个监控应用需要访问自己存储的监控数据,就可以使用客户端凭证模式。

深入 OAuth 2.0 的安全机制

令牌安全

  1. 访问令牌:访问令牌是客户端用于访问受保护资源的凭证。为了保证其安全性,访问令牌应该具备以下特点:
    • 保密性:采用加密算法生成,防止被窃取后直接被解读。例如,使用 JSON Web Tokens(JWT)时,可以对令牌进行签名和加密,只有拥有正确密钥的服务器才能验证和解析令牌。
    • 有效期:设置合理的有效期,过期后自动失效。比如设置访问令牌有效期为 1 小时,这样即使令牌被窃取,攻击者也只有 1 小时的有效时间来利用它。
    • 范围限制:令牌应该明确其允许访问的资源范围。例如,一个令牌可能只允许访问用户的基本信息,而不允许访问用户的支付信息。在授权服务器发放令牌时,需要根据客户端的请求和资源拥有者的授权来准确设置令牌的范围。
  2. 刷新令牌:刷新令牌用于在访问令牌过期后获取新的访问令牌。它同样需要保证安全性:
    • 长期有效性:与访问令牌相比,刷新令牌的有效期通常较长,这样可以减少用户重新授权的频率。但也不能设置过长,以免增加被盗用的风险。
    • 保密性:刷新令牌也应该采用加密存储,并且在传输过程中进行加密。例如,在数据库中存储刷新令牌时,使用加密算法对其进行加密存储。

安全传输

  1. HTTPS:OAuth 2.0 通信过程中,所有涉及敏感信息(如授权码、令牌、用户凭证等)的传输都应该使用 HTTPS 协议。HTTPS 通过 SSL/TLS 加密,防止信息在传输过程中被窃取或篡改。例如,客户端向授权服务器请求令牌的过程:POST https://authorization-server.com/token HTTP/1.1,必须使用 HTTPS 协议,确保请求和响应中的令牌等信息不被泄露。
  2. 防止中间人攻击:除了使用 HTTPS,还可以采取其他措施防止中间人攻击。例如,客户端可以验证授权服务器的证书,确保连接的是真正的授权服务器。在代码实现中,可以使用证书固定(Certificate Pinning)技术,将授权服务器的证书指纹固定在客户端代码中,只有当服务器提供的证书指纹与固定的指纹匹配时,才进行通信。

客户端认证

  1. 客户端密钥:在授权码模式、密码模式和客户端凭证模式中,客户端需要向授权服务器提供客户端密钥。客户端密钥应该妥善保管,通常存储在服务器端的安全位置,如环境变量或密钥管理系统中。例如,在 Python 的 Flask 应用中,可以将客户端密钥存储在 .env 文件中,并通过 python - dotenv 库加载:
from dotenv import load_dotenv
import os

load_dotenv()
client_secret = os.getenv('CLIENT_SECRET')
  1. 客户端标识验证:授权服务器需要验证客户端标识的合法性。可以通过在授权服务器中维护一个合法客户端列表,在接收到客户端请求时,检查客户端标识是否在列表中。例如,在数据库中创建一个 clients 表,存储客户端标识和相关信息,每次收到请求时查询该表进行验证:
CREATE TABLE clients (
    client_id VARCHAR(255) PRIMARY KEY,
    client_secret VARCHAR(255),
    client_name VARCHAR(255),
    redirect_uris TEXT
);
import sqlite3

def validate_client(client_id):
    conn = sqlite3.connect('oauth.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM clients WHERE client_id =?", (client_id,))
    result = cursor.fetchone()
    conn.close()
    return result is not None

OAuth 2.0 代码示例

使用 Python 和 Flask 搭建简单的 OAuth 2.0 授权服务器

  1. 安装依赖
    • 首先需要安装 flaskflask - oauthlib 库。可以使用 pip 进行安装:
pip install flask flask - oauthlib
  1. 创建授权服务器代码
from flask import Flask, request, redirect, session
from flask_oauthlib.provider import OAuth2Provider
import sqlite3

app = Flask(__name__)
app.secret_key = 'your_secret_key'
oauth = OAuth2Provider(app)

# 模拟用户数据库
def get_user(username, password):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE username =? AND password =?", (username, password))
    result = cursor.fetchone()
    conn.close()
    return result

# 模拟客户端数据库
def get_client(client_id):
    conn = sqlite3.connect('clients.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM clients WHERE client_id =?", (client_id,))
    result = cursor.fetchone()
    conn.close()
    return result

@oauth.clientgetter
def load_client(client_id):
    client = get_client(client_id)
    if client:
        return {
            'client_id': client[0],
            'client_secret': client[1],
          'redirect_uris': [client[3]],
            'default_redirect_uri': client[3]
        }
    return None

@oauth.grantgetter
def load_grant(client_id, code):
    conn = sqlite3.connect('grants.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM grants WHERE client_id =? AND code =?", (client_id, code))
    result = cursor.fetchone()
    conn.close()
    if result:
        return {
            'client_id': result[0],
            'code': result[1],
          'redirect_uri': result[2],
            'expires': result[3],
            'user': result[4]
        }
    return None

@oauth.grantsetter
def save_grant(client_id, code, request, *args, **kwargs):
    expires = datetime.utcnow() + timedelta(seconds=100)
    conn = sqlite3.connect('grants.db')
    cursor = conn.cursor()
    cursor.execute("INSERT INTO grants (client_id, code, redirect_uri, expires, user) VALUES (?,?,?,?,?)",
                   (client_id, code, request.redirect_uri, expires, session['user_id']))
    conn.commit()
    conn.close()

@oauth.tokengetter
def load_token(access_token=None, refresh_token=None):
    conn = sqlite3.connect('tokens.db')
    cursor = conn.cursor()
    if access_token:
        cursor.execute("SELECT * FROM tokens WHERE access_token =?", (access_token,))
    else:
        cursor.execute("SELECT * FROM tokens WHERE refresh_token =?", (refresh_token,))
    result = cursor.fetchone()
    conn.close()
    if result:
        return {
            'access_token': result[0],
          'refresh_token': result[1],
            'client_id': result[2],
            'expires': result[3],
            'user': result[4]
        }
    return None

@oauth.tokensetter
def save_token(token, request, *args, **kwargs):
    expires_in = token.get('expires_in')
    expires = datetime.utcnow() + timedelta(seconds=expires_in)
    conn = sqlite3.connect('tokens.db')
    cursor = conn.cursor()
    cursor.execute("DELETE FROM tokens WHERE client_id =? AND user =?", (request.client.client_id, session['user_id']))
    cursor.execute("INSERT INTO tokens (access_token, refresh_token, client_id, expires, user) VALUES (?,?,?,?,?)",
                   (token['access_token'], token['refresh_token'], request.client.client_id, expires, session['user_id']))
    conn.commit()
    conn.close()

@app.route('/authorize', methods=['GET', 'POST'])
@oauth.authorize_handler
def authorize(*args, **kwargs):
    if request.method == 'GET':
        client_id = kwargs.get('client_id')
        client = get_client(client_id)
        if client:
            return render_template('authorize.html', client=client)
        return 'Invalid client', 400
    username = request.form.get('username')
    password = request.form.get('password')
    user = get_user(username, password)
    if user:
        session['user_id'] = user[0]
        return True
    return False

@app.route('/token', methods=['POST'])
@oauth.token_handler
def access_token():
    return None

if __name__ == '__main__':
    app.run(debug=True)
  1. 数据库初始化
    • 需要创建 users.dbclients.dbgrants.dbtokens.db 数据库,并创建相应的表。例如,users.db 中的 users 表:
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(255),
    password VARCHAR(255)
);
- `clients.db` 中的 `clients` 表前面已给出示例。
- `grants.db` 中的 `grants` 表:
CREATE TABLE grants (
    client_id VARCHAR(255),
    code VARCHAR(255),
    redirect_uri TEXT,
    expires DATETIME,
    user INTEGER,
    PRIMARY KEY (client_id, code)
);
- `tokens.db` 中的 `tokens` 表:
CREATE TABLE tokens (
    access_token VARCHAR(255),
    refresh_token VARCHAR(255),
    client_id VARCHAR(255),
    expires DATETIME,
    user INTEGER,
    PRIMARY KEY (access_token)
);

使用 Java 和 Spring Security OAuth2 搭建 OAuth 2.0 资源服务器

  1. 添加依赖:在 pom.xml 文件中添加 Spring Security OAuth2 相关依赖:
<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</groupId>
        <artifactId>spring - security - oauth2 - resource - server</artifactId>
    </dependency>
</dependencies>
  1. 配置资源服务器
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/api/public").permitAll()
               .antMatchers("/api/private").authenticated()
               .anyRequest().denyAll()
               .and()
           .oauth2ResourceServer()
               .jwt();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://authorization - server.com/.well - known/jwks.json").build();
    }
}
  1. 创建资源接口
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ResourceController {

    @GetMapping("/public")
    public String publicResource() {
        return "This is a public resource";
    }

    @GetMapping("/private")
    public String privateResource() {
        return "This is a private resource, only accessible with a valid token";
    }
}

以上代码示例分别展示了使用 Python 和 Java 搭建 OAuth 2.0 授权服务器和资源服务器的基本过程,通过这些示例可以更深入地理解 OAuth 2.0 的工作原理和实现方式。在实际应用中,还需要根据具体需求进行更多的优化和安全配置。

OAuth 2.0 与其他安全机制的结合

与 JSON Web Tokens(JWT)结合

  1. JWT 简介:JSON Web Tokens 是一种用于在各方之间安全传输信息的开放标准(RFC 7519)。它通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。头部包含令牌的类型(如 JWT)和使用的哈希算法,例如:
{
  "alg": "HS256",
  "typ": "JWT"
}
- 载荷是存放实际需要传输的数据的部分,如用户信息、权限等:
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
- 签名是通过使用头部中指定的算法,对头部、载荷以及一个密钥进行签名生成的,用于验证消息在传输过程中没有被更改。

2. 在 OAuth 2.0 中使用 JWT:在 OAuth 2.0 中,访问令牌可以采用 JWT 的形式。这样做的好处是,资源服务器可以直接验证 JWT 的签名,而无需再向授权服务器进行令牌验证,从而提高了验证效率。例如,在 Spring Security OAuth2 中配置使用 JWT 作为访问令牌:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore());
    }

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("your_signing_key");
        return converter;
    }
}

在资源服务器端,可以通过配置 JwtDecoder 来验证 JWT 令牌:

@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/api/public").permitAll()
               .antMatchers("/api/private").authenticated()
               .anyRequest().denyAll()
               .and()
           .oauth2ResourceServer()
               .jwt();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withSecretKey(Base64.getDecoder().decode("your_signing_key")).build();
    }
}

与 OpenID Connect 结合

  1. OpenID Connect 简介:OpenID Connect 是建立在 OAuth 2.0 之上的身份验证层。它在 OAuth 2.0 的基础上,提供了一种可互操作的机制,用于验证最终用户的身份,并以一种简单且安全的方式向依赖方(客户端)提供关于已认证用户的基本信息(如姓名、电子邮件等)。
  2. 结合方式:在 OAuth 2.0 流程中,通过扩展授权请求和响应,OpenID Connect 可以实现身份验证和用户信息获取。例如,客户端在授权请求中添加 scope=openid 来表明需要进行身份验证和获取用户信息。授权服务器在验证通过后,除了返回访问令牌,还会返回 ID Token,ID Token 是一个 JWT,包含了用户的身份信息。
// 在 Spring Security OAuth2 中配置 OpenID Connect
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
          .withClient("client_id")
          .secret("{noop}client_secret")
          .authorizedGrantTypes("authorization_code", "refresh_token")
          .scopes("openid", "profile", "email")
          .redirectUris("http://localhost:8080/callback");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(tokenServices());
    }

    @Bean
    public TokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(false);
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("your_signing_key");
        tokenServices.setAccessTokenConverter(jwtAccessTokenConverter);
        return tokenServices;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("your_signing_key");
        return converter;
    }
}

在客户端,可以通过解析 ID Token 来获取用户信息:

import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
import com.nimbusds.jwt.proc.JWTClaimsSet;
import com.nimbusds.jwt.proc.JWTClaimsVerifier;
import com.nimbusds.jwt.proc.SecurityContext;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.util.Collections;
import java.util.Map;

@Service
public class OpenIDConnectService {

    public Map<String, Object> parseIdToken(String idToken) {
        try {
            SignedJWT signedJWT = SignedJWT.parse(idToken);
            JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
            JWTClaimsVerifier<SecurityContext> verifier = new DefaultJWTClaimsVerifier<>(claimsSet.getHeader(), Collections.emptyList());
            verifier.verify(claimsSet, null);
            return claimsSet.getClaims();
        } catch (ParseException e) {
            throw new RuntimeException("Failed to parse ID Token", e);
        }
    }
}

通过与 JWT 和 OpenID Connect 的结合,OAuth 2.0 可以进一步增强其安全性和功能性,满足更多复杂的应用场景需求。

OAuth 2.0 的应用场景与案例分析

第三方登录

  1. 场景描述:在众多网站和应用中,用户可以选择使用第三方账号(如 Google、Facebook、微信等)进行登录。以使用 Google 账号登录为例,网站(客户端)希望获取用户的基本信息(如姓名、电子邮件)。用户在网站点击使用 Google 登录按钮后,网站将用户重定向到 Google 的授权服务器。用户在 Google 授权服务器进行身份验证,并授权网站访问其基本信息。Google 授权服务器返回授权码给网站,网站使用授权码获取访问令牌,然后使用访问令牌从 Google 的资源服务器获取用户信息。
  2. 优势:对于用户来说,无需在每个网站都注册新账号,提高了用户体验;对于网站来说,减少了用户注册流程,降低了用户流失率,同时可以获取一些基本的用户信息用于个性化服务。

微服务间的授权

  1. 场景描述:在一个大型的微服务架构系统中,不同的微服务可能需要相互访问资源。例如,用户服务存储了用户的基本信息,订单服务在处理订单时可能需要获取用户的地址信息。订单服务作为客户端,向授权服务器请求授权,获取访问令牌后,使用令牌访问用户服务的资源。这样可以确保只有经过授权的微服务才能访问特定的资源,提高系统的安全性。
  2. 实现方式:可以采用 OAuth 2.0 的客户端凭证模式,每个微服务作为一个客户端,在授权服务器进行注册,并获取客户端标识和客户端密钥。当微服务需要访问其他微服务资源时,使用客户端凭证获取访问令牌。例如,在 Spring Cloud 微服务架构中,可以集成 Spring Security OAuth2 来实现微服务间的授权:
# 订单服务的配置文件
spring:
  security:
    oauth2:
      client:
        registration:
          user - service:
            client - id: order - service - client - id
            client - secret: order - service - client - secret
            scope: user:read
            authorization - grant - type: client_credentials
        provider:
          user - service:
            token - uri: http://authorization - server.com/token
// 订单服务中获取用户地址的代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class UserAddressService {

    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;

    public String getUserAddress(String userId) {
        OAuth2AuthenticationToken authentication = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
            authentication.getAuthorizedClientRegistrationId(), authentication.getName());
        String accessToken = authorizedClient.getAccessToken().getTokenValue();
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        HttpEntity<String> entity = new HttpEntity<>(headers);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.exchange(
            "http://user - service.com/api/users/" + userId + "/address",
            HttpMethod.GET,
            entity,
            String.class
        );
        return response.getBody();
    }
}

通过以上应用场景和案例分析,可以看到 OAuth 2.0 在不同领域的广泛应用及其对提高系统安全性和用户体验的重要作用。在实际应用中,需要根据具体的业务需求和安全要求,合理选择和配置 OAuth 2.0 的授权流程和安全机制。