OAuth 2.0 扩展协议之 PKCE

d4c47b126488fcb808e4ddd7732014af.png

前言

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

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 扩展协议推出, 就是为了解决公开客户端的授权安全问题。

授权码拦截攻击

aaa8d7812a2c4658c6255c016da8557e.png

上面是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 协议流程

f4510c0932bd3ed8369b779e77843179.png

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*128unreservedunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"ALPHA = %x41-5A / %x61-7ADIGIT = %x30-39

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

javascript 示例

// Required: Node.js crypto module// https://nodejs.org/api/crypto.html#crypto_cryptofunction 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_cryptofunction 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 czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-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 模式的授权过程

3e119952d4489fe90869bd341b3d2506.gif

References

https://www.rfc-editor.org/rfc/rfc6749
https://www.rfc-editor.org/rfc/rfc7636.html
https://oauth.net/2/pkce
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04

😃 欢迎关注微信公众号【全球技术精选】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295408.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

欧洲杯直播助PPTV日均流量登顶视频行业首位

随着意大利溃败,西班牙最终捧杯,四年一度火爆的欧洲杯赛事也就此落下帷幕。令人欣喜的是,在本届欧洲杯比赛的直播过程中,由于比赛安排再深夜,互联网应用取代传统电视台成为人们观看赛事的主要渠道,各大视频…

深度优先算法

/*** @author Think* 给定整数a1,a2,a3,a4…,判断是否可以从中选出若干数,使他们的和恰好为K*/ public class 深度优先算法 {//n=4,a={1,2,4,7};k=13;public static int n=4;public static int [] a={1,2,4,7};public static int k=13;public static void main(String[] ar…

/etc/network/interfaces

man resolvconf man interfaceshttp://linux-net.osdl.org/index.php/Bridge示例:# This file describes the network interfaces available on your system# and how to activate them. For more information, see interfaces(5).# The loopback network interfaceauto lo ifa…

nashorn js 调用 java_从nashorn(JDK 8 JavaScript引擎)调用char []输入参数调用Java函数?...

我想从Oracle的nashorn JavaScript引擎中调用一个带有 char[] 输入参数的Java函数(非数组参数类型的函数对我来说没问题) .如果我用JavaScript字符串文字调用Java函数,nashorn balksjavax.script.ScriptException: TypeError: Can not invoke method[jdk.internal.d…

Netty之有效规避内存泄漏

有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 肖 直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能,也告别了频繁的GC。但,要重新培养被Java的自动垃圾回收惯坏了的惰性。 Netty有…

.NET 6新特性试用 | record struct

前言在以前的文章中,我们介绍过record类型,它具有不变性(《为什么应该用record来定义DTO》)和值相等性(《为什么应该用record来定义DTO(续)》)。record是引用类型。而在.NET 6中,我们可以使用record struct定义值类型。…

原来我们看到的世界地图竟这样震撼!多年的地理白学了...

▲ 点击查看几乎每个家庭都会有两张地图:一张世界地图,一张中国地图。薄薄的两张纸,蕴藏着让每个人学会“看世界”的磅礴力量。哈佛上一任校长,也是300多年来唯一一位女校长德鲁吉尔平福斯特(Drew Gilpin Faust&#x…

Exchange Powershell查看用户最后登陆邮箱时间

在Exchange日常管理中,我们可能经常会遇到这样的问题,就是怎么来知道一个用户最后的登录时间?这个问题在使用Exchange powershell就能很好的解决了。 用管理员身份运行Exchange management powershell 查看某一个邮箱数据库的统计信息&#x…

在n个火柴里面拿3根出来拼接成最大三角形的周长

求三角形max周长 public class 求三角形max周长 { public static void main(String[] args) {/*** 有n个棍子 每个棍子的长度是a[i]* 3<n<100;* 1<a[i]<100; */ System.out.println("请输入n根绳子"); Scanner input new Scanner(System.in); int ni…

mysql之mysqldump命令

导出要用到MySQL的mysqldump工具&#xff0c;基本用法是&#xff1a; shell> mysqldump [OPTIONS] database [tables] 如果你不给定任何表&#xff0c;整个数据库将被导出。 通过执行mysqldump --help&#xff0c;你能得到你mysqldump的版本支持的选项表。 注意&#xff0c;…

易企秀制作的步骤

2019独角兽企业重金招聘Python工程师标准>>> 1、选图很关键 &#xff08;图片干净 整洁&#xff0c;不同方位展示 &#xff0c;符合主题&#xff09;。 2、配上说明性文字 简明扼要 3、选择合适的模板和背景音乐。 4、及时沟通与调整。 转载于:https://my.oschina.n…

java instanceof运算符_Java instanceof 运算符的使用方法

用法&#xff1a;(类型变量 instanceof 类|接口)作用&#xff1a;instanceof 操作符用于判断前面的对象是否是后面的类&#xff0c;或者其子类、实现类的实例。如果是则返回true 否则就返回false。注意&#xff1a; instanceof前面的操作数的编译时类型要么与后面的类相同&…

C# WPF MVVM模式Prism框架下事件发布与订阅

01—前言处理同模块不同窗体之间的通信和不同模块之间不同窗体的通信&#xff0c;Prism提供了一种事件机制&#xff0c;可以在应用程序中低耦合的模块之间进行通信&#xff0c;该机制基于事件聚合器服务&#xff0c;允许发布者和订阅者之间通过事件进行通讯&#xff0c;且彼此之…

这几部高分学科纪录片,助力孩子涨姿势拓视野~

全世界只有3.14 % 的人关注了爆炸吧知识▌导读本文为同学们整理了几部高分经典学科纪录片&#xff0c;对应文学、数学、经济学、地理、化学。这不仅是课堂学习的补充与延伸&#xff0c;更是开拓视野、激发学习内驱力的绝佳利器。建议收藏&#xff01;&#xff08;关注视频号少年…

Linux 学习_在Linux下面安装tomcat

要在linux下面安装tomcat&#xff0c;首先我们需要做一些准备工作.... 下载tomcat&#xff1a; 下载地址&#xff1a;http://tomcat.apache.org/download-60.cgi 下载&#xff1a;tar.gz 如图&#xff1a; 说明&#xff1a; WinISO安装版&#xff1a;下载地址&#xff1a;http…

Codeforces 474C Captain Marmot 给定4个点和各自旋转中心 问旋转成正方形的次数

题目链接&#xff1a;点击打开链接 题意&#xff1a; 给定T表示case数 以下4行是一个case 每行2个点&#xff0c;u v 每次u能够绕着v逆时针转90 问最少操作多少次使得4个u构成一个正方形。 思路&#xff1a; 枚举判可行 #include <iostream> #include <cmath> #inc…

穷竭搜索

/**穷竭搜索是将所有的可能性罗列出来&#xff0c;在其中找到答案的方法&#xff0c;这里我们主要介绍深度优先搜索和广度优先搜索* author Think**/ public class 穷竭搜索 {public static void main(String[] args) {}//计算阶层 n&#xff01;n*(n-1)&#xff01;public sta…

JQUERY插件学习之jQuery UI

jQuery UI:http://jqueryui.com/ jQuery UI介绍: jQuery UI 是以 jQuery 为基础的开源 JavaScript 网页用户界面代码库。包含底层用户交互、动画、特效和可更换主题的可视控件。我们可以直接用它来构建具有很好交互性的web应用程序。所有插件测试能兼容 IE 6.0, Firefox 3, Saf…

java 数组拼接字符串_如何在java里java字符串数组合并成一个数组?

展开全部java里java字符串数组合并成一个数组方法如下&#xff1a;//方法一 Arrays类String[] a {"A","B","C"};String[] b {"D","E"};// List list Arrays.asList(a); --OK// List list Arrays.asList("A",…

如何通过 C# 判断某个 IP 是否属于某IP段?

咨询区 Ricky&#xff1a;如果判断某一个IP (172.16.11.50) 是否落在某一个 IP 段内&#xff1f;比如这样的段&#xff1a;172.16.11.5 - 100&#xff0c;另外不知道 C# 中是否有现成的轮子可以做这件事 ?回答区 BuddhiP&#xff1a;可以考虑使用 jsakamoto 大佬写的工具包&am…