当前位置:网站首页>【OAuth2】二十、OAuth2扩展协议 PKCE
【OAuth2】二十、OAuth2扩展协议 PKCE
2022-08-10 08:23:00 【北城小林】
一、什么是PKCE
PKCE 全称是 Proof Key for Code Exchange(代码交换证明密钥), 在2015年发布, 它是 OAuth 2.0 核心的一个扩展协议, 所以可以和现有的授权模式结合使用,比如 Authorization Code + PKCE, 这也是最佳实践,PKCE 最初是为移动设备应用和本地应用创建的, 主要是为了减少公共客户端的授权码拦截攻击。
PKCE其实主要是通过在授权的过程中增加了code_challenge和code_verifier两个元素来对整个流程进行验证,防止code被第三方截取的情况。 实际上它的原理是客户端提供一个自创建的证明给授权服务器, 授权服务器通过它来验证客户端,把访问令牌(access_token) 颁发给真实的客户端而不是伪造的。
参考
参考2
1、回顾OAuth2.0授权码模式流程:
https://www.rfc-editor.org/rfc/rfc6749
(A)客户端请求资源所有者授权。 可以直接向资源所有者发出授权请求 (如所示),或者最好通过授权间接地 服务器作为中介。
(B)客户端收到授权授予,这是a 表示资源所有者授权的凭据, 使用本例中定义的四种授权类型之一表示 说明或使用延期授权类型方法使用的方法决定了授权授予类型
客户机请求授权和支持的类型 授权服务器。
客户端通过认证请求访问令牌 授权服务器,并给出授权授权。
(D)授权服务器对客户端进行认证和验证 授权授予,如果有效,将发出访问令牌。
2、 授权码模式

- 1、客户端携带 client_id, scope, redirect_uri, state 等信息引导用户请求授权服务器的授权端点下发 code。
- 2、授权服务器验证客户端身份,验证通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)。
- 3、假设用户同意授权,此时授权服务器会将 code 和 state(如果客户端传递了该参数)拼接在 redirect_uri 后 面,以302(重定向)形式下发 code。
- 4、客户端携带 code, redirect_uri, 以及 client_secret 请求授权服务器的令牌端点下发 access_token。
- 5、授权服务器验证客户端身份,同时验证 code,以及 redirect_uri 是否与请求 code 时相同,验证通过后下发 access_token,并选择性下发 refresh_token,支持令牌的刷新。
参考
3、PKCE的流程
PKCE主要是通过在授权的过程中增加了code_challenge和code_verifier两个元素来对整个流程进行验证,防止code被第三方截取的情况。具体流程如下:
也就是在原来请求的token中添加了code_challenge和code_verifier参数
- code_verifier
一个Client端生成的随机字符串(由字母,数字,- ,. , ,~ 组成)。
调用应用程序(SPA)创建的密钥,该密钥可由授权服务器验证密钥被称为 Code Verifier(代码验证器) - code_challenge
调用应用程序为 Code Verifier 创建一个转换值,称为 Code Challenge,HTTPS 发送该值(Code Challenge)去检索 Authorization Code.
这样就提高了从认证服务器获取 token的安全性。
4、 PKCE的流程说明
- 1、 OAuth2客户端生成 code_verifier,并使用 code_challenge_method 计算 code_challenge,具体的算法稍后会详细讲解。
- 2、OAuth2客户端发起/oauth2/authorize授权请求,携带 code_challenge 和 code_challenge_method 这两个参数。
- 3、OAuth2授权服务器对OAuth2客户端/oauth2/authorize的授权请求进行验证。
- 4、 OAuth2授权服务器检查 code_challenge 和 code_challenge_method 是否存在。
- 5、 如果步骤4存在这两个参数,授权服务器会持久化code_challenge 和 code_challenge_method这两个参数。
- 6、然后OAuth2授权服务器对授权码请求进行响应。
- 7、 OAuth2客户端收到授权码响应开始调用/oauth2/token请求访问令牌,该请求需要额外附加初始请求中生成的code_verifier参数。
- 8、 OAuth2授权服务器收到访问令牌请求,用该请求中携带的code_verifier和步骤5持久化的code_challenge_method进行摘要计算生成一个校验串,该校验串必须和步骤5持久化的code_challenge进行匹配校验。
- 29、如果步骤8中的匹配校验成功,则发放访问令牌access_token,否则该请求被拒绝。
5、PKCE请求演示
5.1 没有PKCE的授权请求:
http://localhost:9000/oauth2/authorize?response_type=code&client_id=felord&scope=message.read message.write&state=46ge_TeI-dHuAnyv67nVmCcAmFgCVSZAqjTi9Om-1aA=&redirect_uri=http://127.0.0.1:8082/test/bar
5.2 有PKCE的授权请求:
http://localhost:9000/oauth2/authorize?response_type=code&client_id=felord&scope=message.read%20message.write&state=NAqBLbmooEhMPGELwleACOHvybODP_hctqV-PuNjuxo%3D&redirect_uri=http://127.0.0.1:8082/test/bar&code_challenge=mQ4xjFrCXg-1P4iURSXcSCmGVc-dloG0b0sdGpICrN0&code_challenge_method=S256
6、Spring Security PKCE的实现
pkce 分支
6.1、 OAuth2 客户端配置
OAuth2客户端要生成code_verifier,并使用 code_challenge_method 计算 code_challenge,并封装到授权请求参数,需要改造OAuth2AuthorizationRequestResolver接口。
@Bean
SecurityFilterChain customSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = accessTokenResponseClient();
//TODO OAuth2客户端测要生成code_verifier,并使用 code_challenge_method 计算 code_challenge,并封装到授权请求参数,需要改造OAuth2AuthorizationRequestResolver接口。
DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
authorizationRequestResolver.setAuthorizationRequestCustomizer(builder -> builder.attributes(attributes -> {
if (!attributes.containsKey(PkceParameterNames.CODE_VERIFIER)) {
String codeVerifier = this.secureKeyGenerator.generateKey();
attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
builder.additionalParameters(additionalParameters -> {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
String codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
} catch (NoSuchAlgorithmException ex) {
// plain 方式 这种方式几乎作废了
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
}
});
}
}));
http.authorizeRequests((requests) -> requests
.antMatchers("/test/bar", "/oauth2/jwks")
.hasAnyAuthority("ROLE_ANONYMOUS", "SCOPE_userinfo")
.anyRequest().authenticated())
//然后把改造好的OAuth2AuthorizationRequestResolver配置到HttpSecurity: .oauth2Login().authorizationEndpoint().authorizationRequestResolver(authorizationRequestResolver)
.and()
// 获取token端点配置 比如根据code 获取 token
.tokenEndpoint().accessTokenResponseClient(accessTokenResponseClient);
http.oauth2Client()
.authorizationCodeGrant().authorizationRequestResolver(authorizationRequestResolver)
.accessTokenResponseClient(accessTokenResponseClient);
return http.build();
}
6.1、 Spring Authorization Server配置
客户端配置要添加 .requireProofKey(true)
private RegisteredClient createJwtRegisteredClient(final String id) {
return RegisteredClient.withId(id)
// 客户端ID和密码
.clientId("testid")
// 名称 可不定义
.clientName("test")
// jwt 断言必备
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
.clientSettings(ClientSettings.builder()
.tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256)
// 开启PKCE
.requireProofKey(true)
// private key jwt
.jwkSetUrl("http://localhost:8082/oauth2/jwks")
.build())
.build();
}
边栏推荐
- Rust学习:6.3_复合类型之元组
- 明明加了唯一索引,为什么还是产生重复数据?
- Relaxation class: the boss will martial arts, who also can not hold up against!The charm of six sigma training
- Rust learning: 6.2_ Tuples of composite types
- 短视频同城流量宣传小魔推有何优势?如何给实体商家带来销量?
- Rust学习:6.4_复合类型之枚举
- IDLE development wordCount program (5)
- Uni-app开发微信小程序使用本地图片做背景图
- Obtain - 65 [chances] : "soldiers, subtlety also - 7-36 meter reading - defeat
- 自动化测试框架Pytest(三)——自定义allure测试报告
猜你喜欢
随机推荐
VMware ESX Server常用命令行
短视频同城流量宣传小魔推有何优势?如何给实体商家带来销量?
SQL SERVER 数据库,表的数据发生增删改,该表的索引会在ldf日志中记录吗?
时序动作定位 | ASM-Loc:弱监督时序动作定位的动作感知片段建模(CVPR 2022)
【Rust指南】使用Cargo工具高效创建Rust项目 | 理解Rust特别的输入输出语句
raid5的写性能,是不的比raid10快一些?
PHP笔记 28 29 30 31
VS2013-调试汇编代码-生成asm文件-结构体内存布局-函数参数压栈-调用约定
自动化测试框架Pytest(三)——自定义allure测试报告
StringUtils的具体操作
高性能短连接设计
PLSQL学习第三天
AFNetworking概述和4.0的实践
基于sklearn的决策树应用实战
If the data of the oracle business table is added, deleted, or modified, will the index of the table write redo and undo?
30条实用MySQL优化法则
CV-人脸识别-2018:ArcFace
上课笔记(7)(1)——#647. 找树根和孩子(root)
debezium-connector-mysql拉起docker报错:debezium启动docke
Compilation failure:找不到符号









