在【One by One系列】IdentityServer4(四)授权码流程中提过一句:
“为了安全,IdentityServer4是带有PKCE支持的授权码模式
”
我们来回顾一下授权码流程
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
--摘自阮一峰老师-理解OAuth 2.0,自认为阮老师这块已经写比较清晰了,正所谓”眼前有景道不得,崔颢题诗在上头“。
1.什么是PKCE
PKCE,全称Proof Key for Code Exchange,上篇讲到SPA,这是一种没有后端服务器的原生客户端,代码都在用户本地设备上运行,比如SPA在用户浏览器上运行,Win/Mac客户端,iOS/Android APP,如果让这些原生客户端安全地存放密钥(client secret)并不现实,且容易被破解。
Implicit Flow:我们没有介绍Implicit Flow,官方最新文档也没有,现在来看,可能就是因为安全的原因。Access Token会直接被传递给Redirect URL,容易被截取Access-Token
Authorization Code Flow:Redirect URL只会接收一个授权码,且授权码必须要和Client ID,Client Secret一同使用才能获取Access Token。然而原生客户端无法安全保存Client Secret,第三方恶意应用可以破解Client Secret,并按上述方法截取Authorization Code,同样不建议使用。
PKCE,旨在提高移动设备上授权代码流程执行过程中的安全性。有关该功能的定义,参阅RFC7636,微软翻译为保护授权码授权。实质是通过密码学技术手段,确保恶意第三方即使截获到授权码(Authorization Code)或者其他密钥,也无法向认证服务器交换获取Access Token。
PKCE要求所有客户端必须需要实现的内容:
随机生成一串字符串,并用URL-Safe的Base64编码处理,结果为:
code_verifier
将
code_verifier
通过SHA256哈希加密,并用URL-Safe的Base64编码处理,结果为:code_challenge
//1.生成code_verifier
let code_verifier=generateRandomString(32);//2.生成code_challenge
let code_challenge=generateCodeChallenge(code_verifier);function generateRandomString(length) {let text = "";let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for (let i = 0; i < length; i++) {text += possible.charAt(Math.floor(Math.random() * possible.length));}return text;
}
function generateCodeChallenge(code_verifier) {return code_challenge = base64URL(CryptoJS.SHA256(code_verifier))
}
2.PKCE授权码流程
那么PKCE支持的授权码流程就发生了变化,具体流程如下:
(A)客户端除了
response_type
,Scope
等标准参数,还必须带上,code_challenge
与code_challenge_method
,发起对服务器的/authorize
端点请求(B)服务器对
/authorize
除了执行标准的OAuth请求验证,还会检查code_challenge
与code_challenge_method
是否存在,并存储(C)服务器返回
Authorization Code
(D)客户端拿到
Authorization Code
,就用Authorization Code
和code_verifier
向服务端的/token
端点发起请求,获取Access-token
(E)服务端对
/token
端点,除了执行标准的OAuth验证外,还会使用客户端传过来的code_verifier
和服务端存储的code_challenge_method
来生成自己的code_challenge
(F)服务端会将生成
code_challenge
与初始请求/authorize
端点的code_challenge
(并根据Authorization Code
存储在服务端)两相比较匹配,颁发
access-token
不匹配,拒绝该请求
流程图
“注意点:
”
上图的t(code_verifier)指的就是code_challenge
code_verifier是一个加密字符串,其所具有的熵必须要足够大,使攻击者无法预测或猜到其值
任何截获到的中间方,并不能由code_challenge
破解出code_verifier
,这里只有客户端知道这两个值。所以截获到code_challenge
和Authorization Code
,也换不来想要的token,换来的只有拒绝。总而言之,这样降低恶意使用Authorization Code与Access-token的行为的风险。
3.查看IdentityServer4授权码流程
知晓了PKCE的男人,现在想对IdentityServer4授权码流程有一个更详细了了解,以及对PKCE的验证,我们使用WireShark对整个请求进行抓包。
“由于fiddler智能抓包一些浏览器请求,对于整个过程中的一些后台请求,并不能捕获,所以我们选择WireShark这个工具。也不能通过浏览器开发者模式,由于授权重定向的过程太快,好多请求都看不清。
”
3.1 运行IdentityServer
cd .\IdentityServer\
dotnet run
3.2 运行wireshark
打开软件,选择-adapter for loopback traffic capture
开始抓包
3.2 运行JavaScript客户端
cd .\JavaScript\
dotnet run
3.2 操作
按照上一篇文章操作,登录,注销
3.3 停止wireshark的捕获
停止捕获,通过自带的列表过滤报文,如下图
这就是整个授权登录,然后注销登录过程中,发生的http请求。
4.详解IdentityServer4授权码流程(SPA)
4.1 请求IdentityServer4的配置端点-获取authorize端点
请求
...
GET /.well-known/openid-configuration HTTP/1.1\r\n
Host: localhost:5001\r\n
...
响应
200 OK
Date: Sun, 12 Jul 2020 17:38:32 GMT
Content-Type: application/json; charset=UTF-8
{"issuer":"http://localhost:5001","jwks_uri":"http://localhost:5001/.well-known/openid-configuration/jwks","authorization_endpoint":"http://localhost:5001/connect/authorize","token_endpoint":"http://localhost:5001/connect/token","userinfo_endpoint":"http://localhost:5001/connect/userinfo","end_session_endpoint":"http://localhost:5001/connect/endsession","check_session_iframe":"http://localhost:5001/connect/checksession","revocation_endpoint":"http://localhost:5001/connect/revocation","introspection_endpoint":"http://localhost:5001/connect/introspect","device_authorization_endpoint":"http://localhost:5001/connect/deviceauthorization","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["openid","profile","api1","offline_access"],"claims_supported":["sub","name","family_name","given_name","middle_name","nickname","preferred_username","profile","picture","website","gender","birthdate","zoneinfo","locale","updated_at"],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","password","urn:ietf:params:oauth:grant-type:device_code"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"id_token_signing_alg_values_supported":["RS256"],"subject_types_supported":["public"],"code_challenge_methods_supported":["plain","S256"],"request_parameter_supported":true}
这里主要是获取授权端点,由oidc-client.js内部触发。
4.2 请求authorize端点
请求
GET /connect/authorize?client_id=js&redirect_uri=http%3A%2F%2Flocalhost%3A6003%2Fcallback.html&response_type=code&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&code_challenge=kz0v3GRm8yFhmjoOYBWX2pg68N5waBd0hFVvbvuIT9E&code_challenge_method=S256&response_mode=query HTTP/1.1
Host: localhost:5001
响应
Status Code: 302
Location: http://localhost:5001/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Djs%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A6003%252Fcallback.html%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520api1%26state%3De9292cf7e4d04c03bf3fc7fe230f0378%26code_challenge%3Dkz0v3GRm8yFhmjoOYBWX2pg68N5waBd0hFVvbvuIT9E%26code_challenge_method%3DS256%26response_mode%3Dquery
请求授权端点,带上了response_type
,scope
,profile
,code_challenge
,code_challenge_method
,响应302重定向,
4.3 跳转登录页
请求
GET /Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Djs%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A6003%252Fcallback.html%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520api1%26state%3De9292cf7e4d04c03bf3fc7fe230f0378%26code_challenge%3Dkz0v3GRm8yFhmjoOYBWX2pg68N5waBd0hFVvbvuIT9E%26code_challenge_method%3DS256%26response_mode%3Dquery HTTP/1.1
Host: localhost:5001
响应
Status Code: 200
HTTP/1.1 200 OK
<!DOCTYPE html>
<html lang="en">
</html>
重定向至登录页,响应登录页html
4.4 登录操作
请求
POST /Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Djs%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A6003%252Fcallback.html%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520api1%26state%3De9292cf7e4d04c
POST /Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Djs%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A6003%252Fcallback.html%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520api1%26state%3De9292cf7e4d04c03bf3fc7fe230f0378%26code_challenge%3Dkz0v3GRm8yFhmjoOYBWX2pg68N5waBd0hFVvbvuIT9E%26code_challenge_method%3DS256%26response_mode%3Dquery HTTP/1.1
Host: localhost:5001
Content-Type: application/x-www-form-urlencoded
ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Djs%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A6003%252Fcallback.html%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520api1%26state%3De9292cf7e4d04c03bf3fc7fe230f0378%26code_challenge%3Dkz0v3GRm8yFhmjoOYBWX2pg68N5waBd0hFVvbvuIT9E%26code_challenge_method%3DS256%26response_mode%3Dquery&Username=admin&Password=admin123456%21&button=login&__RequestVerificationToken=CfDJ8EX5EDLzf1lFsdLD-61W3Pr-hyJIt5MjRQdemuM-9ab4QyRWG2omEwKHp0SZhc2a_ZjsoJEzge4QvrP_zVvkfQHq5bAki5KFTo1upo251HiQvnsFMp4ptx0KnmoHxBbIjDhBQ2VRh4fQCyJJiM791Nk&RememberLogin=false
响应
Status Code: 302
Location: /connect/authorize/callback?client_id=js&redirect_uri=http%3A%2F%2Flocalhost%3A6003%2Fcallback.html&response_type=code&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&code_challenge=kz0v3GRm8yFhm
Set-Cookie: idsrv.session=3CEABBB62BE1F6DDD7B793A8F5BF1803; path=/; samesite=none\r\n
登录操作,在原有的参数基础上,增加Username
,Password
,Post提交,响应302重定向,并Set-Cookie
4.5 验证授权,回调返回授权码
请求
GET /connect/authorize/callback?client_id=js&redirect_uri=http%3A%2F%2Flocalhost%3A6003%2Fcallback.html&response_type=code&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&code_challenge=kz0v3GRm8yFhmjoOYBWX2pg68N5waBd0
Host: localhost:5001\r\n
Cookie:Cookie pair: idsrv.session=3CEABBB62BE1F6DDD7B793A8F5BF1803
响应
Status Code: 302
Location: http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-dit
Set-Cookie: idsrv=CfDJ8EX5EDLzf1lFsdLD-61W3PpyiWLfH7YweLeh_CTmjIJRzGGwIfhBAMhZaVaL24gZ5erWj5v38WA2DjcIqPPnSphi70aR_HZ9qnShmYfKkzrSmGTptJSESS6tanbw2RggxRPraoLf6PbthzsNJ3QZqZpB9AWJRxVvKtmq7-YFC-Kv11KHE_UHPBM3Hkpmulb35BqJ2wTumJwo8HdcJJdCPnzY0UqWCkv9wyUhQ2djvAzNceJKFiigwFsGWubmVuCse5hvp_QYLmwRdsobPP-0gSk7GnvCd6-r3ybcxYdvy2m_2XjpG1V3h34zefvLDw4sLeofllFJ5L4PUVhnqCd7DyKGb_xMAjHXvDiKgR32eCX9EZ8k1WsZsbH0NsX0xEKOlRboNNWM6mw_LXD3b_hZhDR00UbAmfjXapU6Sjss65HIPJXMhJ9HrVUleoNeFVUW-I61kd-FjXal9UxxOjv_cc5sYeIGxTnNkfT1Nc4wqQaip8ulkK0O2xYJWjPMeR-VA6rdwsvOa_iAq9fqY5KF2t4AzSoOMb62O2nwzqZZU1lpHxB5x86D0EjRUVnoR9CLfQ; path=/; samesite=none; httponly
验证用户名、密码,服务端cookie已设置,验证成功,再次响应302,重定向客户端页面callback.html,并Set-Cookie
4.6 请求IdentityServer4的配置端点-获取token端点
请求
GET /.well-known/openid-configuration HTTP/1.1
Host: localhost:5001
Sec-Fetch-Mode: cors
Referer: http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-ditlWq7kYgZrgydLbAQ.C34DCC1705940F3F7C5D34685AB51C9F
响应
200 OK
Date: Sun, 12 Jul 2020 17:38:32 GMT
Content-Type: application/json; charset=UTF-8
{"issuer":"http://localhost:5001","jwks_uri":"http://localhost:5001/.well-known/openid-configuration/jwks","authorization_endpoint":"http://localhost:5001/connect/authorize","token_endpoint":"http://localhost:5001/connect/token","userinfo_endpoint":"http://localhost:5001/connect/userinfo","end_session_endpoint":"http://localhost:5001/connect/endsession","check_session_iframe":"http://localhost:5001/connect/checksession","revocation_endpoint":"http://localhost:5001/connect/revocation","introspection_endpoint":"http://localhost:5001/connect/introspect","device_authorization_endpoint":"http://localhost:5001/connect/deviceauthorization","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["openid","profile","api1","offline_access"],"claims_supported":["sub","name","family_name","given_name","middle_name","nickname","preferred_username","profile","picture","website","gender","birthdate","zoneinfo","locale","updated_at"],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","password","urn:ietf:params:oauth:grant-type:device_code"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"id_token_signing_alg_values_supported":["RS256"],"subject_types_supported":["public"],"code_challenge_methods_supported":["plain","S256"],"request_parameter_supported":true}
获取token端点值
4.7 请求token端点
请求
POST /connect/token HTTP/1.1
Host: localhost:5001
Content-Type: application/x-www-form-urlencoded
Sec-Fetch-Mode: cors
Referer: http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-ditlWq7kYgZrgydLbAQ.C34DCC1705940F3F7C5D34685AB51C9F
Accept-Encoding: gzip, deflate, br
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8client_id=js&code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&redirect_uri=http%3A%2F%2Flocalhost%3A6003%2Fcallback.html&code_verifier=e661671994bd4eb89bb9e4da214e34f0970c24c9a36e41fe9e05aec433c604c2f721da29216345b0898445e5e105c86f&grant_type=authorization_code
响应
Status Code: 200
{"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc2OEFDMTBCNEEzNzI4RTIwNjQ0MDU4QjZFREY1MDUxIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1OTQ1NzU1MTIsImV4cCI6MTU5NDU3NTgxMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiYXVkIjoianMiLCJpYXQiOjE1OTQ1NzU1MTIsImF0X2hhc2giOiJhaU9rX05xeDh0OERXeTc5cFo3WFhBIiwic19oYXNoIjoiWkllczRfaldVMEFBaTI0ZUY4Yjg1dyIsInNpZCI6IjNDRUFCQkI2MkJFMUY2REREN0I3OTNBOEY1QkYxODAzIiwic3ViIjoiMSIsImF1dGhfdGltZSI6MTU5NDU3NTUxMiwiaWRwIjoibG9jYWwiLCJhbXIiOlsicHdkIl19.VmdopUrWH4rcrJp3nXcE-LLNodowQh6n0-HjN_aZZNOj1xPECwG_g0-nY9N9q-jNeapkIrWk2U2Y9liUXuBAOLHhT0Txou4dNhAdMIvYJ8SKRSgh06SEbpGT_hDtN345YZd9IJSjGWo3q_B04p03pw6S92tQp6ae74v1mMAgCskVnKth0SWpPqUPSuZjSdlcuzhA7OvXlz3wmeGJPu5c0jC1BBzrrM1_WmFvUCmwCo9q3Z0MLcz6eq1JZafhSkkRSAgTJdWIdrq6w7Yj1DInETebOhJrt3Yl7jGVAjJqK1WMnJym3J4n9d5GYfv9wA4eu3GgKvG_rax1GjgtV3zR0g","access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc2OEFDMTBCNEEzNzI4RTIwNjQ0MDU4QjZFREY1MDUxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTQ1NzU1MTIsImV4cCI6MTU5NDU3OTExMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiY2xpZW50X2lkIjoianMiLCJzdWIiOiIxIiwiYXV0aF90aW1lIjoxNTk0NTc1NTEyLCJpZHAiOiJsb2NhbCIsImp0aSI6IkU3OTdDOTI0QUJFOTI1QTQ3N0Y1NDVCQUVFRkRGMUE3Iiwic2lkIjoiM0NFQUJCQjYyQkUxRjZEREQ3Qjc5M0E4RjVCRjE4MDMiLCJpYXQiOjE1OTQ1NzU1MTIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJhcGkxIl0sImFtciI6WyJwd2QiXX0.Cl4u3bU9bm0mG9qjn52WwstbPmuhBetKkEIRgVENIU_4hurJ8fRPiNc3zzl0tzgIw0_yvHy8eyA6EyVfSMyQZ77ao0TjEkBcTu62H7eKTHWrdKyp0eEhcxRiVvAYAQcFP2NPva8z0zZiVUUnE0q6-WiE7P_hDF8Ljs6AyYAS4khWX9iG-WoSlqDlalOo7ohU7gGleIpnlH5LUvQpkDVHbOzCNviJH6r4VbqT7llnIDNNjMwy9cwh3TJcYNsFZjTL3jsQtmbNv9ajmNBZKhWvGSRN_6ywgbPcL54FEqVTe3hfwdcSjHCSV2Owzcu6at8UplAm-Kd0T09ay6ChXWz67g","expires_in": 3600,"token_type": "Bearer","scope": "openid profile api1"
}
以code
,scope
,client_id
,code_verifier
,grant_type
等必要参数作为post参数请求token端点,返回id_token
、access_token
、expires_in
、scope
等.
4.8 请求userinfo端点
预检请求
OPTIONS /connect/userinfo HTTP/1.1
Host: localhost:5001
Referer: http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-ditlWq7kYgZrgydLbAQ.C34DCC1705940F3F7C5D34685AB51C9F
响应
Status Code: 204
Access-Control-Allow-Headers: authorization\r\n
Access-Control-Allow-Methods: GET\r\n
Access-Control-Allow-Origin: http://localhost:6003\r\n
正式请求
Host: localhost:5001
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc2OEFDMTBCNEEzNzI4RTIwNjQ0MDU4QjZFREY1MDUxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTQ1NzU1MTIsImV4cCI6MTU5NDU3OTExMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiY2xpZW50X2lkIjoianMiLCJzdWIiOiIxIiwiYXV0aF90aW1lIjoxNTk0NTc1NTEyLCJpZHAiOiJsb2NhbCIsImp0aSI6IkU3OTdDOTI0QUJFOTI1QTQ3N0Y1NDVCQUVFRkRGMUE3Iiwic2lkIjoiM0NFQUJCQjYyQkUxRjZEREQ3Qjc5M0E4RjVCRjE4MDMiLCJpYXQiOjE1OTQ1NzU1MTIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJhcGkxIl0sImFtciI6WyJwd2QiXX0.Cl4u3bU9bm0mG9qjn52WwstbPmuhBetKkEIRgVENIU_4hurJ8fRPiNc3zzl0tzgIw0_yvHy8eyA6EyVfSMyQZ77ao0TjEkBcTu62H7eKTHWrdKyp0eEhcxRiVvAYAQcFP2NPva8z0zZiVUUnE0q6-WiE7P_hDF8Ljs6AyYAS4khWX9iG-WoSlqDlalOo7ohU7gGleIpnlH5LUvQpkDVHbOzCNviJH6r4VbqT7llnIDNNjMwy9cwh3TJcYNsFZjTL3jsQtmbNv9ajmNBZKhWvGSRN_6ywgbPcL54FEqVTe3hfwdcSjHCSV2Owzcu6at8UplAm-Kd0T09ay6ChXWz67g
Referer: http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-ditlWq7kYgZrgydLbAQ.C34DCC1705940F3F7C5D34685AB51C9F
Accept-Encoding: gzip, deflate, br
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
响应
Status Code: 200
{"name": "RandyField","given_name": "Randy","family_name": ["Field","Randy"],"website": "http://www.randyfield.cn","sub": "1"
}
4.9 请求checksession端点
请求
GET /connect/checksession HTTP/1.1
Host: localhost:5001
Cookie: .AspNetCore.Antiforgery.oLdxsluyV7s=CfDJ8EX5EDLzf1lFsdLD-61W3PplD2jEFwzyz4r_NCYMSVOLnWqf5HrxwUtY6ouOI2VHKD9vJdF48KUqkNQeTHXe3hrm8uf1GL_v1917E6-Km7WF3V06G4cBHNvrlJQuQF7k__7FPhPVySgmKgwqfAPky8w; idsrv.session=3CEABBB62BE1F6DDD7B793A8F5BF1803; idsrv=CfDJ8EX5EDLzf1lFsdLD-61W3PpyiWLfH7YweLeh_CTmjIJRzGGwIfhBAMhZaVaL24gZ5erWj5v38WA2DjcIqPPnSphi70aR_HZ9qnShmYfKkzrSmGTptJSESS6tanbw2RggxRPraoLf6PbthzsNJ3QZqZpB9AWJRxVvKtmq7-YFC-Kv11KHE_UHPBM3Hkpmulb35BqJ2wTumJwo8HdcJJdCPnzY0UqWCkv9wyUhQ2djvAzNceJKFiigwFsGWubmVuCse5hvp_QYLmwRdsobPP-0gSk7GnvCd6-r3ybcxYdvy2m_2XjpG1V3h34zefvLDw4sLeofllFJ5L4PUVhnqCd7DyKGb_xMAjHXvDiKgR32eCX9EZ8k1WsZsbH0NsX0xEKOlRboNNWM6mw_LXD3b_hZhDR00UbAmfjXapU6Sjss65HIPJXMhJ9HrVUleoNeFVUW-I61kd-FjXal9UxxOjv_cc5sYeIGxTnNkfT1Nc4wqQaip8ulkK0O2xYJWjPMeR-VA6rdwsvOa_iAq9fqY5KF2t4AzSoOMb62O2nwzqZZU1lpHxB5x86D0EjRUVnoR9CLfQ
响应
Status Code: 200
Content-Type: text/html; charset=UTF-8<!DOCTYPE html>
<!--Copyright (c) Brock Allen & Dominick Baier. All rights reserved.-->
<!--Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.-->
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<title>Check Session IFrame</title>
</head>
<body>
</body>
</html>
之后又请求了一次/.well-known/openid-configuration
,/connect/checksession
第一次与第二次/connect/checksession
几乎一样的请求与响应,唯一的差别
Referer
不同第一次:http://localhost:6003/callback.html?code=4E902EE0335BDED6D89E3775C836D3BC42848124BD1B0FCFA688CB91731C2DD5&scope=openid%20profile%20api1&state=e9292cf7e4d04c03bf3fc7fe230f0378&session_state=MP5-ftEtQxdTRi7Pw1a6HnU-ditlWq7kYgZrgydLbAQ.C34DCC1705940F3F7C5D34685AB51C9F
第二次:http://localhost:6003/index.html
从结果推导过程,第一次,是回调页面callback.html
,请求了/connect/checksession
,获取了一个iframe的html,callback.html中window.location = "index.html";
由会重定向到index.html
,可以推导出,这里是帮index.html请求渲染内容,因为index中有一个隐藏的iframe,如图
两次checksession之间还有一次/.well-known/openid-configuration请求,这次Referer已经是index.html
,说明已经发生重定向了,重定向之后,由于document
中有这个iframe
,自然就会再发起一次请求,所以这两次的Referer
参数不一样。
4.10 注销登录操作
①.请求endsession端点
请求
GET /connect/endsession?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc2OEFDMTBCNEEzNzI4RTIwNjQ0MDU4QjZFREY1MDUxIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1OTQ1NzU1MTIsImV4cCI6MTU5NDU3NTgxMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIiwiYXVkIjoianMiLCJpYXQiOjE1OTQ1NzU1MTIsImF0X2hhc2giOiJhaU9rX05xeDh0OERXeTc5cFo3WFhBIiwic19oYXNoIjoiWkllczRfaldVMEFBaTI0ZUY4Yjg1dyIsInNpZCI6IjNDRUFCQkI2MkJFMUY2REREN0I3OTNBOEY1QkYxODAzIiwic3ViIjoiMSIsImF1dGhfdGltZSI6MTU5NDU3NTUxMiwiaWRwIjoibG9jYWwiLCJhbXIiOlsicHdkIl19.VmdopUrWH4rcrJp3nXcE-LLNodowQh6n0-HjN_aZZNOj1xPECwG_g0-nY9N9q-jNeapkIrWk2U2Y9liUXuBAOLHhT0Txou4dNhAdMIvYJ8SKRSgh06SEbpGT_hDtN345YZd9IJSjGWo3q_B04p03pw6S92tQp6ae74v1mMAgCskVnKth0SWpPqUPSuZjSdlcuzhA7OvXlz3wmeGJPu5c0jC1BBzrrM1_WmFvUCmwCo9q3Z0MLcz6eq1JZafhSkkRSAgTJdWIdrq6w7Yj1DInETebOhJrt3Yl7jGVAjJqK1WMnJym3J4n9d5GYfv9wA4eu3GgKvG_rax1GjgtV3zR0g&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A6003%2Findex.html
Host: localhost:5001
Connection: keep-alive
Cookie: .AspNetCore.Antiforgery.oLdxsluyV7s=CfDJ8EX5EDLzf1lFsdLD-61W3PplD2jEFwzyz4r_NCYMSVOLnWqf5HrxwUtY6ouOI2VHKD9vJdF48KUqkNQeTHXe3hrm8uf1GL_v1917E6-Km7WF3V06G4cBHNvrlJQuQF7k__7FPhPVySgmKgwqfAPky8w; idsrv.session=3CEABBB62BE1F6DDD7B793A8F5BF1803; idsrv=CfDJ8EX5EDLzf1lFsdLD-61W3PpyiWLfH7YweLeh_CTmjIJRzGGwIfhBAMhZaVaL24gZ5erWj5v38WA2DjcIqPPnSphi70aR_HZ9qnShmYfKkzrSmGTptJSESS6tanbw2RggxRPraoLf6PbthzsNJ3QZqZpB9AWJRxVvKtmq7-YFC-Kv11KHE_UHPBM3Hkpmulb35BqJ2wTumJwo8HdcJJdCPnzY0UqWCkv9wyUhQ2djvAzNceJKFiigwFsGWubmVuCse5hvp_QYLmwRdsobPP-0gSk7GnvCd6-r3ybcxYdvy2m_2XjpG1V3h34zefvLDw4sLeofllFJ5L4PUVhnqCd7DyKGb_xMAjHXvDiKgR32eCX9EZ8k1WsZsbH0NsX0xEKOlRboNNWM6mw_LXD3b_hZhDR00UbAmfjXapU6Sjss65HIPJXMhJ9HrVUleoNeFVUW-I61kd-FjXal9UxxOjv_cc5sYeIGxTnNkfT1Nc4wqQaip8ulkK0O2xYJWjPMeR-VA6rdwsvOa_iAq9fqY5KF2t4AzSoOMb62O2nwzqZZU1lpHxB5x86D0EjRUVnoR9CLfQ
响应
Status Code: 302
Location: http://localhost:5001/Account/Logout?logoutId=CfDJ8EX5EDLzf1lFsdLD-61W3Pq-tO1b1UEAB4vvmYhY4sokyl8em0HlVYGX9otqLmnglBnG6V_RukkVflCbku5Elb72VsJkntUDrh6G1AP1ctnkCWhiuN9lIBouTf9swekyYrj8H0Q-5iHISwsYmXz00kEPqWR1-7-DoXbv2g3Dxtt3fPxVN5WmFd0-I7zuLoyPrpiqz62TYGUNygB1qOt0BXsvwVLWyl_amuMVbqUgJkvkbS4049YYVK7W0fl55L66mDnBEF5ktdixHE9ld3_dso-4FL8ppa-wdUq9Wy6JPo5p1S4BIf_LCfX0Cp4eDVow5sgVtpPfbamRX-pRHco_-H8jifrDg5xVmxupY6NAMgzK8Sbn6lZhW_KjkkpTyWRiE7kfc_uaYAeykfDGqvmAfGq7-9suIwGd3vvJw2-pn6aVuzTH4o3SycophXWv-5BUYQ
请求服务端endsession端点,传递参数id-token,响应302重定向注销登录页面。
②.请求Logout页面
请求
GET /Account/Logout?logoutId=CfDJ8EX5EDLzf1lFsdLD-61W3Pq-tO1b1UEAB4vvmYhY4sokyl8em0HlVYGX9otqLmnglBnG6V_RukkVflCbku5Elb72VsJkntUDrh6G1AP1ctnkCWhiuN9lIBouTf9swekyYrj8H0Q-5iHISwsYmXz00kEPqWR1-7-DoXbv2g3Dxtt3fPxVN5WmFd0-I7zuLoyPrpiqz62TYGUNygB1qOt0BXsvwVLWyl_amuMVbqUgJkvkbS4049YYVK7W0fl55L66mDnBEF5ktdixHE9ld3_dso-4FL8ppa-wdUq9Wy6JPo5p1S4BIf_LCfX0Cp4eDVow5sgVtpPfbamRX-pRHco_-H8jifrDg5xVmxupY6NAMgzK8Sbn6lZhW_KjkkpTyWRiE7kfc_uaYAeykfDGqvmAfGq7-9suIwGd3vvJw2-pn6aVuzTH4o3SycophXWv-5BUYQ HTTP/1.1
Host: localhost:5001
Cookie: .AspNetCore.Antiforgery.oLdxsluyV7s=CfDJ8EX5EDLzf1lFsdLD-61W3PplD2jEFwzyz4r_NCYMSVOLnWqf5HrxwUtY6ouOI2VHKD9vJdF48KUqkNQeTHXe3hrm8uf1GL_v1917E6-Km7WF3V06G4cBHNvrlJQuQF7k__7FPhPVySgmKgwqfAPky8w; idsrv.session=3CEABBB62BE1F6DDD7B793A8F5BF1803; idsrv=CfDJ8EX5EDLzf1lFsdLD-61W3PpyiWLfH7YweLeh_CTmjIJRzGGwIfhBAMhZaVaL24gZ5erWj5v38WA2DjcIqPPnSphi70aR_HZ9qnShmYfKkzrSmGTptJSESS6tanbw2RggxRPraoLf6PbthzsNJ3QZqZpB9AWJRxVvKtmq7-YFC-Kv11KHE_UHPBM3Hkpmulb35BqJ2wTumJwo8HdcJJdCPnzY0UqWCkv9wyUhQ2djvAzNceJKFiigwFsGWubmVuCse5hvp_QYLmwRdsobPP-0gSk7GnvCd6-r3ybcxYdvy2m_2XjpG1V3h34zefvLDw4sLeofllFJ5L4PUVhnqCd7DyKGb_xMAjHXvDiKgR32eCX9EZ8k1WsZsbH0NsX0xEKOlRboNNWM6mw_LXD3b_hZhDR00UbAmfjXapU6Sjss65HIPJXMhJ9HrVUleoNeFVUW-I61kd-FjXal9UxxOjv_cc5sYeIGxTnNkfT1Nc4wqQaip8ulkK0O2xYJWjPMeR-VA6rdwsvOa_iAq9fqY5KF2t4AzSoOMb62O2nwzqZZU1lpHxB5x86D0EjRUVnoR9CLfQ
响应
Status Code: 200
Content-Type: text/html; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: idsrv.session=.; expires=Fri, 12 Jul 2019 17:38:40 GMT; path=/; samesite=none
Set-Cookie: idsrv=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=none; httponly
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" /><title>IdentityServer4</title> <link rel="icon" type="image/x-icon" href="/favicon.ico" /><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /><link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css" /><link rel="stylesheet" href="/css/site.css" />
</head>
<body>
<div class="nav-page"><nav class="navbar navbar-expand-lg navbar-dark bg-dark"><a href="/" class="navbar-brand"><img src="/icon.png" class="icon-banner">IdentityServer4</a> </nav>
</div><div class="container body-container">
<div class="logged-out-page"><h1>Logout<small>You are now logged out</small></h1><div>Click <a class="PostLogoutRedirectUri" href="http://localhost:6003/index.html">here</a> to return to the<span>JavaScript Client</span> application.</div><iframe width="0" height="0" class="signout" src="http://localhost:5001/connect/endsession/callback?endSessionId=CfDJ8EX5EDLzf1lFsdLD-61W3Ppjdh7zkU7fvaRvtKK_djZ_wTALkKC4YyDjpZmbnIsfrQa2BTfaRz1AkiCNlJLVYT2mUZA0Os9WyOBB1QDhYOjscWlomm6RWpUDy4L8tjr1mTdkH0T5IXOUsF7wcRrP-6ssERsPyswxqg9bFwkjuYlZnQYZOFSdFwGV8T3uru7BVhP8HywA6JeyYdUC-CQl_02vdiN3h5_bfxcP6sSbuu0kq8Dvs1GRXZT2B813Wy7DI_fmnWwyMiy2isjdpZjxx2E"></iframe>
</div></div><script src="/lib/jquery/dist/jquery.slim.min.js"></script><script src="/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
重定向至Logout页面,并通过Set-Cookie
把IndentityServer-localhost:5001
的cookie置为空,且响应一段包含隐藏src="http://localhost:5001/connect/endsession/callback"的iframe的html
③.触发endsession回调
请求
GET /connect/endsession/callback?endSessionId=CfDJ8EX5EDLzf1lFsdLD-61W3Ppjdh7zkU7fvaRvtKK_djZ_wTALkKC4YyDjpZmbnIsfrQa2BTfaRz1AkiCNlJLVYT2mUZA0Os9WyOBB1QDhYOjscWlomm6RWpUDy4L8tjr1mTdkH0T5IXOUsF7wcRrP-6ssERsPyswxqg9bFwkjuYlZnQYZOFSdFwGV8T3uru7BVhP8HywA6JeyYdUC-CQl_02vdiN3h5_bfxcP6sSbuu0kq8Dvs1GRXZT2B813Wy7DI_fmnWwyMiy2isjdpZjxx2E HTTP/1.1
Host: localhost:5001
Sec-Fetch-Dest: iframe
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-
Cookie: .AspNetCore.Antiforgery.oLdxsluyV7s=CfDJ8EX5EDLzf1lFsdLD-61W3PplD2jEFwzyz4r_NCYMSVOLnWqf5HrxwUtY6ouOI2VHKD9vJdF48KUqkNQeTHXe3hrm8uf1GL_v1917E6-Km7WF3V06G4cBHNvrlJQuQF7k__7FPhPVySgmKgwqfAPky8w
响应
Status Code: 200
Date: Sun, 12 Jul 2020 17:38:39 GMT
Content-Type: text/html; charset=UTF-8
Server: Kestrel
Cache-Control: no-store, no-cache, max-age=0
Pragma: no-cache
Transfer-Encoding: chunked
Content-Security-Policy: default-src 'none'; style-src 'sha256-u+OupXgfekP+x/f6rMdoEAspPCYUtca912isERnoEjY='
X-Content-Security-Policy: default-src 'none'; style-src 'sha256-u+OupXgfekP+x/f6rMdoEAspPCYUtca912isERnoEjY='
<!DOCTYPE html><html><style>iframe{display:none;width:0;height:0;}</style><body></body></html>
触发回调,cookie已置为空
5.总结
这里面确实是PKCE的授权码模式,其次整个客户端(SPA)与服务端交互过程有很多骚操作,比如在html里面返回一段隐藏的iframe,从而触发回调。下一篇,我们将会继续讨论在MVC应用中的IdentityServer4授权码流程,同样是PKCE,但是同样具有一些奇技淫巧的骚操作,待你我共赏。
“对了,有个小贴士,网易有道词典跟WireShark有冲突,打开就是未响应,使用时,关闭有道词典即可,这个坑简直了,真是王大锤的万万没想到。
”
参考链接
https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
https://www.zhihu.com/question/31896659
https://tonyxu.io/zh/posts/2018/oauth2-pkce-flow/
https://www.cnblogs.com/hubwang2020/p/12671712.html