1 Star 0 Fork 2

zhangmm / spring-authorization-server

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
README-pkce.md 8.04 KB
一键复制 编辑 原始数据 按行查看 历史
heguangchuan 提交于 2023-12-11 16:42 . pkce获取acessToken

概述

原文来自 : https://zhuanlan.zhihu.com/p/433472843

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

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

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

授权码拦截攻击

Image

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

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

Image

code_verifier

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

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

code_challenge_method

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

code_challenge

使用 code_challenge_method 对 code_verifier 进行转换得到 code_challenge

原理分析

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

PKCE 是怎么做的呢? 既然固定的 client_secret 是不安全的, 那就每次请求生成一个随机的密钥(code_verifier), 第一次请求到授权服务器的 authorize endpoint时, 携带 code_challenge 和 code_challenge_method, 也就是 code_verifier 转换后的值和转换方法, 然后授权服务器需要把这两个参数缓存起来, 第二次请求到 token endpoint 时, 携带生成的随机密钥的原始值 (code_verifier) , 然后授权服务器进行验证,通过后才颁发令牌, 那向授权服务器 authorize endpoint 和 token endpoint 发起的这两次请求,该如何关联起来呢? 通过 授权码 code 即可, 所以就算恶意程序拦截到了授权码 code, 但是没有 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-urlencoded

https://www.authorization-server.com/oauth2/token?
grant_type=authorization_code
&code=d8c2afe6ecca004eb4bd7024
&redirect_uri=https://www.client.com/callback
&code_verifier=2D9RWc5iTdtejle7GTMzQ9Mg15InNmqk3GZL-Hg5Iz0

配置pkce客户端

https://tonyxu-io.github.io/pkce-generator/

需要添加一个公共客户端并且设置proof key支持,需要准备好的 code_verifier 和 code challenge,可以使用上面的网站生成

  • code verifier: jzrdwRz_F58fqet3SzEDX_gnFeJbDAo3uEyGm_UFtKE
  • code challenge: fkvR4xhVr4wjnXV1DiFyyiHZZUeeC-WdYmfZ7AlRWP0
RegisteredClient pkce=RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("pkce-client")
        .clientSecret("{noop}pkce-client-secret")
        .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
        .redirectUri("https://www.baidu.com")
        .scope(OidcScopes.OPENID)
        .scope(OidcScopes.PROFILE)
        .scope("message.read")
        //设置客户端 使用 proofKey
        .clientSettings(ClientSettings.builder().requireProofKey(true).build())
        .build();

访问授权接口

http://127.0.0.1:9000/oauth2/authorize?client_id=pkce-client&response_type=code&scope=message.read openid&redirect_uri=https://www.baidu.com&code_challenge=fkvR4xhVr4wjnXV1DiFyyiHZZUeeC-WdYmfZ7AlRWP0&code_challenge_method=S256

授权服务器将引导用户至登录界面进行登录,在配置文件中配置了 abc/abc 用户,登录即可,登录后授权服务器将直接重定向到 百度 ,并带上code

https://www.baidu.com/?code=_G2leudiM9ceC3aIXfXHJFnvPj0vNL8XYtLBuCHF_LHENXZ9Bqpl1S5ThG3Fj2lqCt0BLOhkTYvqHPClFiV_l-KOep2Ho8JCLVhxrQ13VmeJY4qYFZB9L3nNX58IOPmv

获取AccessToken

使用上面授权服务器重定向时的 code 参数访问 access token

curl --location --request POST 'localhost:9000/oauth2/token?grant_type=authorization_code&redirect_uri=https://www.baidu.com&code=0o2dyZifIIx81tGRRGII5bYUfWRv4jmmNoCoA-rt6VZidfZgg8_jTtN-SOhvZ-9uofNHYT1khtCTRInDJdMSCH9JiHRqSLkE7HBwORVBWUJve-sKCtlSE8iTTigNoN18&client_id=pkce-client&code_verifier=jzrdwRz_F58fqet3SzEDX_gnFeJbDAo3uEyGm_UFtKE'

Image

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/zhangmm/spring-authorization-server.git
git@gitee.com:zhangmm/spring-authorization-server.git
zhangmm
spring-authorization-server
spring-authorization-server
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891