【转】OAuth 2.0 扩展协议之 PKCE

2023-11-03 07:59
文章标签 协议 扩展 2.0 oauth pkce

本文主要是介绍【转】OAuth 2.0 扩展协议之 PKCE,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

zOAuth 2.0 扩展协议之 PKCE

转自:OAuth 2.0 扩展协议之 PKCE - SpringLeee - 博客园 (cnblogs.com)

前言

阅读本文前需要了解 OAuth 2.0 授权协议的相关内容, 可以参考我的上一篇文章 OAuth 2.0 的探险之旅。

PKCE 全称是 Proof Key for Code Exchange, 在2015年发布, 它是 OAuth 2.0 核心的一个扩展协议, 所以可以和现有的授权模式结合使用,比如 Authorization Code + PKCE, 这也是最佳实践,PKCE 最初是为移动设备应用和本地应用创建的, 主要是为了减少公共客户端的授权码拦截攻击。

在最新的 OAuth 2.1 规范中(草案), 推荐所有客户端都使用 PKCE, 而不仅仅是公共客户端, 并且移除了 Implicit 隐式和 Password 模式, 那之前使用这两种模式的客户端怎么办? 是的, 您现在都可以尝试使用 Authorization Code + PKCE 的授权模式。那 PKCE 为什么有这种魔力呢? 实际上它的原理是客户端提供一个自创建的证明给授权服务器, 授权服务器通过它来验证客户端,把访问令牌(access_token) 颁发给真实的客户端而不是伪造的。

客户端类型

上面说到了 PKCE 主要是为了减少公共客户端的授权码拦截攻击, 那就有必要介绍下两种客户端类型了。

OAuth 2.0 核心规范定义了两种客户端类型, confidential 机密的, 和 public 公开的, 区分这两种类型的方法是, 判断这个客户端是否有能力维护自己的机密性凭据 client_secret。

  • confidential
    对于一个普通的web站点来说,虽然用户可以访问到前端页面, 但是数据都来自服务器的后端api服务, 前端只是获取授权码code, 通过 code 换取access_token 这一步是在后端的api完成的, 由于是内部的服务器, 客户端有能力维护密码或者密钥信息, 这种是机密的的客户端。

  • public
    客户端本身没有能力保存密钥信息, 比如桌面软件, 手机App, 单页面程序(SPA), 因为这些应用是发布出去的, 实际上也就没有安全可言, 恶意攻击者可以通过反编译等手段查看到客户端的密钥, 这种是公开的客户端。

在 OAuth 2.0 授权码模式(Authorization Code)中, 客户端通过授权码code向授权服务器获取访问令牌(access_token) 时,同时还需要在请求中携带客户端密钥(client_secret), 授权服务器对其进行验证, 保证 access_token 颁发给了合法的客户端, 对于公开的客户端来说, 本身就有密钥泄露的风险, 所以就不能使用常规 OAuth 2.0 的授权码模式, 于是就针对这种不能使用 client_secret 的场景, 衍生出了 Implicit 隐式模式, 这种模式从一开始就是不安全的。在经过一段时间之后, PKCE 扩展协议推出, 就是为了解决公开客户端的授权安全问题。

授权码拦截攻击

上面是OAuth 2.0 授权码模式的完整流程, 授权码拦截攻击就是图中的C步骤发生的, 也就是授权服务器返回给客户端授权码的时候, 这么多步骤中为什么 C 步骤是不安全的呢? 在 OAuth 2.0 核心规范中, 要求授权服务器的 anthorize endpoint 和 token endpoint 必须使用 TLS(安全传输层协议)保护, 但是授权服务器携带授权码code返回到客户端的回调地址时, 有可能不受TLS 的保护, 恶意程序就可以在这个过程中拦截授权码code, 拿到 code 之后, 接下来就是通过 code 向授权服务器换取访问令牌 access_token , 对于机密的客户端来说, 请求 access_token 时需要携带客户端的密钥 client_secret , 而密钥保存在后端服务器上, 所以恶意程序通过拦截拿到授权码code 也没有用, 而对于公开的客户端(手机App, 桌面应用)来说, 本身没有能力保护 client_secret, 因为可以通过反编译等手段, 拿到客户端 client_secret, 也就可以通过授权码 code 换取 access_token, 到这一步,恶意应用就可以拿着 token 请求资源服务器了。

state 参数, 在 OAuth 2.0 核心协议中, 通过 code 换取 token 步骤中, 推荐使用 state 参数, 把请求和响应关联起来, 可以防止跨站点请求伪造-CSRF攻击, 但是 state 并不能防止上面的授权码拦截攻击,因为请求和响应并没有被伪造, 而是响应的授权码被恶意程序拦截。

PKCE 协议流程

PKCE 协议本身是对 OAuth 2.0 的扩展, 它和之前的授权码流程大体上是一致的, 区别在于, 在向授权服务器的 authorize endpoint 请求时,需要额外的 code_challenge 和 code_challenge_method 参数, 向 token endpoint 请求时, 需要额外的 code_verifier 参数, 最后授权服务器会对这三个参数进行对比验证, 通过后颁发令牌。

code_verifier

对于每一个OAuth 授权请求, 客户端会先创建一个代码验证器 code_verifier, 这是一个高熵加密的随机字符串, 使用URI 非保留字符 (Unreserved characters), 范围 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~", 因为非保留字符在传递时不需要进行 URL 编码, 并且 code_verifier 的长度最小是 43, 最大是 128, code_verifier 要具有足够的熵它是难以猜测的。

code_verifier 的扩充巴科斯范式 (ABNF) 如下:

code-verifier = 43*128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

简单点说就是在 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 范围内,生成43-128位的随机字符串。

javascript 示例

// Required: Node.js crypto module
// https://nodejs.org/api/crypto.html#crypto_crypto
function base64URLEncode(str) {return str.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
var verifier = base64URLEncode(crypto.randomBytes(32));

java 示例

// Required: Apache Commons Codec
// https://commons.apache.org/proper/commons-codec/
// Import the Base64 class.
// import org.apache.commons.codec.binary.Base64;
SecureRandom sr = new SecureRandom();
byte[] code = new byte[32];
sr.nextBytes(code);
String verifier = Base64.getUrlEncoder().withoutPadding().encodeToString(code); 

c# 示例


public static string randomDataBase64url(int length)
{RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();byte[] bytes = new byte[length];rng.GetBytes(bytes);return base64urlencodeNoPadding(bytes);
}public static string base64urlencodeNoPadding(byte[] buffer)
{string base64 = Convert.ToBase64String(buffer);base64 = base64.Replace("+", "-");base64 = base64.Replace("/", "_");base64 = base64.Replace("=", "");return base64;
}string code_verifier = randomDataBase64url(32);

code_challenge_method

对 code_verifier 进行转换的方法, 这个参数会传给授权服务器, 并且授权服务器会记住这个参数, 颁发令牌的时候进行对比, code_challenge == code_challenge_method(code_verifier) , 若一致则颁发令牌。

code_challenge_method 可以设置为 plain (原始值) 或者 S256 (sha256哈希)。

code_challenge

使用 code_challenge_method 对 code_verifier 进行转换得到 code_challenge, 可以使用下面的方式进行转换

  • plain
    code_challenge = code_verifier

  • S256
    code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

客户端应该首先考虑使用 S256 进行转换, 如果不支持,才使用 plain , 此时 code_challenge 和 code_verifier 的值相等。

javascript 示例

// Required: Node.js crypto module
// https://nodejs.org/api/crypto.html#crypto_crypto
function sha256(buffer) {return crypto.createHash('sha256').update(buffer).digest();
}
var challenge = base64URLEncode(sha256(verifier));

java 示例

// Dependency: Apache Commons Codec
// https://commons.apache.org/proper/commons-codec/
// Import the Base64 class.
// import org.apache.commons.codec.binary.Base64;
byte[] bytes = verifier.getBytes("US-ASCII");
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
String challenge = Base64.encodeBase64URLSafeString(digest);

C# 示例

public static string base64urlencodeNoPadding(byte[] buffer)
{string base64 = Convert.ToBase64String(buffer);base64 = base64.Replace("+", "-");base64 = base64.Replace("/", "_");base64 = base64.Replace("=", "");return base64;
}string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));

原理分析

上面我们说了授权码拦截攻击, 它是指在整个授权流程中, 只需要拦截到从授权服务器回调给客户端的授权码 code, 就可以去授权服务器申请令牌了, 因为客户端是公开的, 就算有密钥 client_secret 也是形同虚设, 恶意程序拿到访问令牌后, 就可以光明正大的请求资源服务器了。

PKCE 是怎么做的呢? 既然固定的 client_secret 是不安全的, 那就每次请求生成一个随机的密钥(code_verifier), 第一次请求到授权服务器的 authorize endpoint时, 携带 code_challenge 和 code_challenge_method, 也就是 code_verifier 转换后的值和转换方法, 然后授权服务器需要把这两个参数缓存起来, 第二次请求到 token endpoint 时, 携带生成的随机密钥的原始值 (code_verifier) , 然后授权服务器使用下面的方法进行验证:

  • plain
    code_challenge = code_verifier

  • S256
    code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

通过后才颁发令牌, 那向授权服务器 authorize endpoint 和 token endpoint 发起的这两次请求,该如何关联起来呢? 通过 授权码 code 即可, 所以就算恶意程序拦截到了授权码 code, 但是没有 code_verifier, 也是不能获取访问令牌的, 当然 PKCE 也可以用在机密(confidential)的客户端, 那就是 client_secret + code_verifier 双重密钥了。

最后看一下请求参数的示例:

GET /oauth2/authorize 
https://www.authorization-server.com/oauth2/authorize?
response_type=code
&client_id=s6BhdRkqt3
&scope=user
&state=8b815ab1d177f5c8e 
&redirect_uri=https://www.client.com/callback
&code_challenge_method=S256 
&code_challenge=FWOeBX6Qw_krhUE2M0lOIH3jcxaZzfs5J4jtai5hOX4
POST /oauth2/token  
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencodedhttps://www.authorization-server.com/oauth2/token?
grant_type=authorization_code
&code=d8c2afe6ecca004eb4bd7024
&redirect_uri=https://www.client.com/callback
&code_verifier=2D9RWc5iTdtejle7GTMzQ9Mg15InNmqk3GZL-Hg5Iz0

下边使用 Postman 演示了使用 PKCE 模式的授权过程

References

RFC 6749: The OAuth 2.0 Authorization Framework
RFC 7636: Proof Key for Code Exchange by OAuth Public Clients
PKCE for OAuth 2.0
draft-ietf-oauth-v2-1-04

这篇关于【转】OAuth 2.0 扩展协议之 PKCE的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/336730

相关文章

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

Java常用注解扩展对比举例详解

《Java常用注解扩展对比举例详解》:本文主要介绍Java常用注解扩展对比的相关资料,提供了丰富的代码示例,并总结了最佳实践建议,帮助开发者更好地理解和应用这些注解,需要的朋友可以参考下... 目录一、@Controller 与 @RestController 对比二、使用 @Data 与 不使用 @Dat

Spring组件初始化扩展点BeanPostProcessor的作用详解

《Spring组件初始化扩展点BeanPostProcessor的作用详解》本文通过实战案例和常见应用场景详细介绍了BeanPostProcessor的使用,并强调了其在Spring扩展中的重要性,感... 目录一、概述二、BeanPostProcessor的作用三、核心方法解析1、postProcessB

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。