区分认证和授权分别是 authentication 和 authorization:
认证,就是身份的认证;而授权,则是资源的授权。
Ref
OAuth官网:OAuth 2.0 — OAuth
OAuth Guide:The Modern Guide To OAuth (fusionauth.io)
Keycloak:OpenJDK - Keycloak
Auth0:JWT详解结合OIDC格式详解
jwt解析:JSON Web Tokens - jwt.io
FusionAuth:
总结:
- OAuth和ODIC协议内容和流程:https://www.infoq.cn/article/euvhttyf3jmfakmm8cmn
- *JWT、ODIC等认证授权总结:jwt、oauth2和oidc等认证授权技术的理解 - 涂宗勋 - 博客园 (cnblogs.com)
密码学
这里主要是使用签名算法。
JWT(Point)
JSON Web Token,就是关于token描述的通用规范和数据格式。
jwt是一种token规范,那么在用户登录的时候,就可以生成这种规范的token,这样在后续解码、签名验签、数据处理等需求上就可以减少很多的沟通成本。
但是jwt只是处理token规范的问题,却不处理其他token规范之外业务的问题,比如token如何能方便刷新的问题,比如究竟是简单地登录业务,还是涉及到第三方服务的授权业务,还是带有授权和认证的业务等等。
jwt将服务器认证后生成的信息封装到一个json对象中,发送给用户,用户就可以拿着这个json对象向服务器请求其他信息。
jwt数据结构分为:头部、负载和签名。三者通过Base64转码后,用.组合到一起。
各部分的内容中,头部包括加密算法、类型等,负载包括签发时间、过期时间等,签名就是对整个json对象的签名,防止篡改。
一个示例如下:
token示例
服务器将头、负载,加上选定的密钥,使用加密算法将其前面。
JWT使用:将其放入HTTP头部,Authorization: Bearer <token>中即可。
比如在Flaks中使用@jwt-required,就会自动检验token的情况。
OAuth
概念
OAuth 2.0是一种授权协议,用于授权第三方应用程序访问资源,而不需要向第三方应用程序提供凭证。OAuth 2.0协议通过授权流程,让用户向第三方应用程序提供对他们的身份和资源的访问权限。
OAuth 2.0包含的认证类型:
- Authorization Code 授权码
- PKCE 授权码模式增强版
- Client Credentials 客户凭证
- Device Code
- Refresh Token
- Implicit Flow 隐式流程(Legacy)
- Password Grant 密码模式(Legacy)
OAuth 2.0中包含以下主要组件:
- 客户端:需要访问资源的第三方应用程序。
- 授权服务器:验证用户身份和授权请求,并返回访问令牌。
- 资源服务器:存储受保护资源和检查访问令牌的有效性。
- 用户:拥有资源的数据主人,可以授权访问权限给第三方应用程序。
OAuth 2.0协议具有很多优点和用途,例如以下几点:
- 增加了安全性:每个客户端都可以获得它所需的最小特权范围,并且完全控制用户授权的内容。
- 简化了授权:OAuth 2.0协议实现了统一授权流程,使得使用方便。
- 支持多种场景:OAuth 2.0协议可以用于资源所有者与资源服务器不在同一个系统上的情况,也可以用于单点登录、用户授权等多种场景。
OAuth2的授权码模式和简化模式流程
OAuth2.0 授权码流程
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为"code"
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
- code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- client_id:表示客户端ID,必选项。
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
简化模式(隐式授权)
(A)客户端将用户导向认证服务器。
(B)用户决定是否给于客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
(F)浏览器执行上一步获得的脚本,提取出令牌。
(G)浏览器将令牌发给客户端。
OAuth2 token
oauth2标准的token有两个:access_token和refresh_token。
access_token用来访问资源时授权,包含基础授权信息,refresh_token的作用是在access_token失效后直接续签access_token,即上边说的如何方便刷新和续签token的问题。
客户端发出更新令牌的HTTP请求,包含以下参数:
- granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。
- refresh_token:表示早前收到的更新令牌,必选项。
- scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
OIDC
概念
OIDC(OpenID Connect)是一种身份验证协议,基于OAuth 2.0协议,为Web应用程序和移动应用程序提供了一种简单的、标准的方式来实现客户端身份验证和授权。OIDC向OAuth 2.0协议添加了身份验证层,允许客户端获取有关已认证用户的信息,如姓名和电子邮件地址,同时保护用户隐私。
OIDC分为三个主要组件:客户端(例如,Web应用程序或移动应用程序)、认证服务器和用户身份提供者。 其中,客户端和认证服务器之间通过OAuth 2.0的授权流程进行通信,而认证服务器和用户身份提供者之间则通过OpenID Connect协议进行通信。
OpenID Connect协议流程如下:
- 客户端向认证服务器发起身份验证请求。
- 认证服务器要求用户向其提供身份验证信息,可能会要求用户输入用户名和密码或使用双因素身份验证。
- 认证服务器验证用户的身份,并返回一个ID令牌和Access令牌,以及用户的身份信息(例如,姓名和电子邮件地址)。
- 客户端使用Access令牌向资源服务器请求受保护资源。
- 资源服务器验证Access令牌,如果有效则返回所请求的受保护资源。
在OIDC协议中,ID令牌是最重要的令牌,因为它包含有关用户的身份信息。客户端使用ID令牌获取有关已认证用户的信息,以及Access令牌用于访问资源服务器。
总之,OIDC是一种简单且安全的方式来实现客户端身份验证和授权,并且使得客户端能够获取有关已认证用户的信息。
具体的用法和实现上就是,OIDC在oauth2的基础上又加了一个token,叫做id_token。
id_token同样的遵循jwt规范,只不过里边的内容是能够体现用户身份的信息。
因此,OIDC里就会有三个token:access_token、refresh_token和id_token。
OIDC认证流程(Point)
同样分为基于授权码、和隐式流程。
基于授权码的请求
这种方式使用OAuth2的Authorization Code的方式来完成用户身份认证,所有的Token都是通过Token EndPoint(OAuth2中定义:https://tools.ietf.org/html/rfc6749#section-3.2)来发放的。构建一个OIDC的Authentication Request需要提供如下的参数:
- scope:必须。OIDC的请求必须包含值为“openid”的scope的参数。
- response_type:必选。同OAuth2。
- client_id:必选。同OAuth2。
- redirect_uri:必选。同OAuth2。
- state:推荐。同OAuth2。防止CSRF, XSRF。
响应:
在授权服务器接收到认证请求之后,需要对请求参数做严格的验证,具体的规则参见http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation,验证通过后引导EU进行身份认证并且同意授权。在这一切都完成后,会重定向到RP指定的回调地址,并且把code和state参数传递过去。比如:
HTTP/1.1 302 Found Location: https://client.example.org/cb? code=SplxlOBeZQQYbYS6WxSbIA &state=af0ifjsldkj
获取id token:
RP使用上一步获得的code来请求Token EndPoint,这一步同OAuth2,就不再展开细说了。然后Token EndPoint会返回响应的Token,其中除了OAuth2规定的部分数据外,还会附加一个id_token的字段。id_token字段就是上面提到的ID Token。例如:
HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "access_token": "SlAV32hkKG", "token_type": "Bearer", "refresh_token": "8xLOxBtZp8", "expires_in": 3600, "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5 NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4 XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg" }
CSRF(Point)
在OIDC中的防护:
为了防止 OIDC 的 Authentication Request 过程中的 CSRF/XSRF 攻击,可以按以下步骤进行:
- 使用随机数生成器生成一个 Crypto-Secure 级别的随机字符串,作为 state 参数的值。
- 将这个随机字符串保存在当前用户的 session 中。
- 将这个随机字符串作为 OIDC 的 Authentication Request 请求中的 state 参数的值。
- 在返回认证服务器的认证结果时,认证服务器会包含原始的 state 参数值。检查这个原始的 state 参数值是否与当前用户的 session 中保存的值相同。
- 如果原始 state 参数值与保存在 session 中的不同,则认为这是一个 CSRF/XSRF 攻击,拒绝认证请求,并记录日志进行审计。
在 OIDC 流程中,随机字符串通常是由客户端(例如Web应用程序)产生并附加在认证请求(Authentication Request)中的一个参数。OIDC 规范中定义,客户端应该在每次认证请求中使用一个随机生成的不可预测的 state 参数值,并将该值存储在客户端本地或会话中,以便认证响应中的 state 参数可以与客户端上送的相应参数进行比对,从而防止 CSRF/XSRF 攻击。
因此,在 OIDC 流程中,随机字符串通常是由客户端产生的,并在认证请求中带上。在客户端内部,可以使用代码库或开源框架提供的随机数生成函数来生成随机字符串,并确保在 OIDC 认证流程中与 state 参数一起使用,以增加认证请求的安全性和可靠性。
在flask jwt required中,防护方式:JWT Locations — flask-jwt-extended 4.4.4 documentation --- JWT 位置 — 烧瓶-JWT 扩展 4.4.4 文档
主要是通过双重提交,即再增加一个CSRF_TOKEN。其中ACCESS_TOKEN(其中封装了CSRF_TOKEN)存在一个HTTP-ONLY的cookie中,不能被js访问,CSRF_TOKEN放在另一个cookie中,能够被js访问。请求flask API时,除了发送ACCESS_TOKEN的cookie以外,还必须在request header中包含CSRF_TOKEN(这个是前端通过js查询第二个cookie读取出来)。
bearer token
Bearer Token是OAuth 2.0协议中的一种授权令牌类型,通常用于用户授权资格认证和保护API请求。Bearer Token是一种类型的访问令牌,用于确认请求方对于API请求是否有访问权限。
Bearer Token 通过 HTTP 请求头中的Authorization字段进行传递。在Authorization字段中,Bearer Token的值以Bearer关键字开头,后面跟着一个空格,然后是令牌本身。例如:
Bearer Token最大的优点是便于使用和传递。Bearer Token不需要事先向授权服务器进行身份验证,因此适用于临时使用的、短期的令牌。另外,Bearer Token也支持使用HTTPS进行保护,从而确保请求和响应的机密性和完整性。
需要注意的是,Bearer Token具有一定的风险性。由于Bearer Token不需要进行额外的身份验证,所以如果令牌丢失或被盗,攻击者可以使用该令牌来模拟请求方的身份进行API请求。因此,Bearer Token应该被视为机密信息,需要采取相应的安全措施来保护。
KeyCloak
介绍
Keycloak是一种开源的身份和访问管理解决方案,支持OpenID Connect和OAuth 2.0等协议。下面将介绍Keycloak对于OAuth 2.0的实现及其使用流程。Keycloak还提供了多种OAuth 2.0相关的功能,如多租户支持、客户端管理、授权范围控制、JWT签名和加密等。
这里先只介绍4个最常用的核心概念:
Users
: 用户,使用并需要登录系统的对象
Roles
: 角色,用来对用户的权限进行管理
Clients
: 客户端,需要接入Keycloak并被Keycloak保护的应用和服务
Realms
: 领域,领域管理着一批用户、证书、角色、组等,一个用户只能属于并且能登陆到一个域,域之间是互相独立隔离的, 一个域只能管理它下面所属的用户
安装使用
KeyCloak安装使用:OpenJDK - Keycloak
主要步骤是这样的:下载安装-启动-创建admin用户-登录admin控制台-创建域-创建用户-创建应用。
基本都是默认即可,注意为顺利完成一次授权(access_token),简单的认证配置为(新版UI):
然后通过这个URL获取所有属性:
然后就可以通过token_endpoint获取token
http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/token
填写参数为(POST、password模式、账户密码):
ref:
- 不过这个例子主要是对客户端授权的:13-SpringSecurity:OpenID与Keycloak - 掘金 (juejin.cn)
KeyCloak使用问题
- 新老版本的UI问题。找了很久没有配置授权服务器的完整信息,只能通过这个URL获取信息http://127.0.0.1:8080/realms/test-realm/.well-known/openid-configuration。
- KeyCloak可以尝试不同的模式,对应于OAuth2协议不同模型。但是基于OIDC最简单的情况只需要通过账户密码获取access_token即可。客户端授权模式使用不到。
高级特性
实战测试
准备
完成前面KeyCloak安装和基本使用。
这里会使用jwt.io进行jwt的解析查看,以及重新封装。
有个比较有意思的是,正向解析jwt的过程中,对于RS256非对称加密,jwt在最下面signature会显示公钥的信息。其实是jwt.io会根据头部key id和iss的地址,根据标准的实现寻找jwks 端点获取公钥。即jwt.io会向iss的.well-known/openid-configuration发送HTTP。参考here.
复习授权模式(这里主要讨论主要是认证部分,即OIDC的扩展版本。普通OAuth是类似的):
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了多种授权方式。
- Authorization Code 授权码
- Client Credentials 客户凭证
- Refresh Token
- Implicit Flow 隐式流程(Legacy)
- Password Grant 密码模式(Legacy)
分别尝试对以上模式基于keycloak实现进行测试。
使用flask jwt extended进行测试。
Password Grant 密码模式-Legacy
密码授予类型是一种将用户凭据交换为访问令牌的传统方式。由于客户端应用程序必须收集用户的密码并将其发送到授权服务器,因此建议不再使用此授权。 最新的 OAuth 2.0 安全当前最佳实践完全不允许密码授予,并且 OAuth 2.1 中未定义授予。
KeyCloak。注意Client authentication不开启,否则无法直接必须附加上Client Credentials.
请求HTTP:
返回
KeyCloak创建时选择协议,默认就是使用OIDC协议。所以要获取OIDC协议中ID Token,只要加上请求的scope。参考OIDC官网:
scopeREQUIRED. OpenID Connect requests MUST contain the openid scope value. If the openid scope value is not present, the behavior is entirely unspecified.
OIDC
Response
解析ID TOKEN。
使用测试。将上面的access_token或id token作为bearer token发送给jwt required保护的接口,即可正常访问。对于jwt required的保护而言。检查很简单,因为jwt就是无状态的。用指定的alg方法和提供的public key,即可验证jwt的有效性(是否发生篡改)、检查claim是否到期等。
这里有点奇怪的是,flask jwt extended库,要求用户一开始就提供JWT_ALGORITHM和JWT_PUBLIC_KEY。但是JWT_ALGORITHM其实明明可以从jwt的头部读出来。有点不理解。
至于使用refresh token。一样的道理。refresh token的alg是HS256,而我们这里指定的JWT_ALGORITHM是RS256(一般都是非对称加密,keycloak中默认)。那自然是解不开的。
Client Credentials 客户凭证
KeyCloak要勾上Client authentication和service accounts roles。同时credentials方法设置client ID和secret.
请求。
返回。
这里可以看到 "preferred_username": "service-account-springsecurity"。请求的没有用户,而是client本身了。
这里扩展可以看client credential的类型,最常规的是client ID和client secret.
再探讨一下Signed JWT。官网说明。说明简单的要死。
• 客户端必须有私钥和证书。对于 Keycloak,这可以通过传统的keystore
文件获得,该文件可以在客户端应用程序的类路径或文件系统的某处获得。(也可以通过keycloak生成) •在身份验证期间,客户端生成一个 JWT 令牌并使用其私钥对其进行签名,并在特定的反向通道请求(例如,code-to-token 请求)中将其发送到client_assertion
参数中的 Keycloak。 •Keycloak必须有客户端的公钥或证书,才能在JWT上验证签名。在 Keycloak 中,您需要为您的客户端配置客户端凭据。首先,您需要在管理控制台的Credentials
选项卡中选择Signed JWT
作为对您的客户端进行身份验证的方法。然后你可以在标签Keys
中选择: ◦ 以 PEM 格式、JWK 格式或从密钥库上传客户端的公钥或证书。使用此选项,公钥是硬编码的,并且在客户端生成新密钥对时必须更改。如果您没有自己的密钥库,您甚至可以从 Keycloak 管理控制台生成自己的密钥库。有关如何设置 Keycloak 管理控制台的更多详细信息,请参阅服务器管理指南。
总结来说就是,客户端需要私钥加密出JWT,通过发送JWT到keycloak的token endpoint,keycloak需要匹配的公钥,用公钥判断jwt是否有效。主要参数如下:
流程如下:
- 一般都用非对称加密,RS256.
- 在keycloak中生成秘钥。生成的是keystore,包含口令访问。保存选PKCS12,格式更通用。
- 使用以下命令提取私钥.from here
- 拿着pcks8,去生成jwt。jwt需要包含的参数如下。from here
可以到jwt.io中生成。示例如下:
header:
payload:
在signature的private key中,添加前面生成的pkcs8.
- 构造HTTP请求
- Response
解析和secret的结果是一样的。
Refresh Token
当访问令牌过期时,客户端使用刷新令牌授权类型将刷新令牌交换为访问令牌。这允许客户端继续拥有有效的访问令牌,而无需与用户进一步交互。
这个本身就简单多了。就是用前面给的refresh token发过去即可。注意refresh token默认用的是HS256对称加密。因为这个token不需要被别人使用,唯一作用就是用户在合适的时候把这个token发回授权服务器,索要下一个access token.
HTTP: 客户端如果是机密的,认证方法同上。(refresh code作为各个方法的补充,比如授权码模式的补充,那如果授权码要求机密,要验证客户端身份,这里refresh code也是要的)
Response同上。
看看refresh token的组成:
Authorization Code 授权码
机密客户端和公共客户端使用授权代码授予类型来交换访问令牌的授权代码。用户通过重定向 URL 返回到客户端后,应用程序将从 URL 获取授权代码,并使用它来请求访问令牌。
步骤(from openid connect)
- Client prepares an Authentication Request containing the desired request parameters.
- Client sends the request to the Authorization Server.
- Authorization Server Authenticates the End-User.
- Authorization Server obtains End-User Consent/Authorization.
- Authorization Server sends the End-User back to the Client with an Authorization Code.
- Client requests a response using the Authorization Code at the Token Endpoint.
- Client receives a response that contains an ID Token and Access Token in the response body.
- Client validates the ID token and retrieves the End-User's Subject Identifier.
按照官方QuickStart给的例子来说明。
- 步骤1 prepare:打开https://www.keycloak.org/app/,后输入自己realm和client后,点击保存,此时该示例网站记录到要交互的授权服务器相关信息。https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity
默认测试的本地授权服务器在localhost:8080. 只要知道realm名称和client id,就可以组织发送请求到授权服务器的auth端点。(其他url是固定的)
- 步骤2 send request:点击sign in. 第一次客户端发送: http://localhost:8080/realms/test-realm/protocol/openid-connect/auth?client_id=springsecurity&redirect_uri=https%3A%2F%2Fwww.keycloak.org%2Fapp%2F%23url%3Dhttp%3A%2F%2Flocalhost%3A8080%26realm%3Dtest-realm%26client%3Dspringsecurity&state=42c9b640-316b-458c-a737-a028b305010c&response_mode=fragment&response_type=code&scope=openid&nonce=e9c51064-0ca2-4431-bd9a-17d6e84d424c
- client id标识访问的客户端身份。
- redirect uri是oauth2协议标准。因为授权服务器会用户提供认证信息,授权服务器认证通过后,将code这个敏感信息返回,但是不能直接作为HTTP reponse请求返回,避免浏览器、UserAgent看到(参考前面的流程图),而是跳转到客户端中事先在授权服务器注册好的一个URL。表示这个code仅交给可信任方。
- scope。对于oidc协议,必须为openid
auth_endpoint是:http://localhost:8080/realms/test-realm/protocol/openid-connect/auth,可以在configuration中看到。必要的授权请求参数由:client_id、redirect_uri、response_type、scope=openid。
点击signin发出这个请求后,后续就会跳转到授权服务器的范畴。
- 步骤3 Authenticates the End-User。进入auth端点。在auth端点中,提供用户认证信息,主要就是输入账户密码进行用户认证。
- 步骤4. 授权服务器验证用户身份。基于以下的请求,返回302
http://localhost:8080/realms/test-realm/login-actions/authenticate?session_code=gYbTaTk3T2l1e5PlNTGagAfFoxKXUIHqyvXRLLzOuZI&execution=93556663-1af2-40f4-bf21-226420ccc36a&client_id=springsecurity&tab_id=BKIiR459ebs
这个时候同时看到把identify这些token已经返回到cookie里面了,当然这个cookie是HTTP Only的,同时也只是授权服务器在本地的cookie。
- 步骤5验证成功后,返回授权码到前面的redirect url。即点击sign in后,会跳回前面https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity的URL。
根据上一个302的请求,可以看到返回的Location为:
https://www.keycloak.org/app/#url=http://localhost:8080&realm=test-realm&client=springsecurity&state=744225f8-4403-4f1b-8ed1-9baefd570c25&session_state=578b2846-6c5c-4906-9f47-417661d13018&code=6ff466cb-3f09-4b18-a5b3-6ed0500f6b23.578b2846-6c5c-4906-9f47-417661d13018.f5635350-9dce-47aa-85cd-296e268daa40
重点是返回了code。
- 步骤6. 客户端使用这个code,组织发送新的请求(客户端需要提供redirect url这个接口,当从授权服务器返回调用这个接口时,这个接口取出code,再获取token)。
请求:http://localhost:8080/realms/test-realm/protocol/openid-connect/token
- 步骤7. 收到授权服务器提供的token.
再基于一个纯postman的示例。
- 对授权服务器的auth端点,构建一个请求。
http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=springsecurity&redirect_uri=http://olimi.icu
这个redirect我用自己的网站,跳转后手动拿到code再自己发送一次请求即可。(不要用百度!百度这家伙拿到code的那次请求还真给我用了,code用一次就用不了了。)当然了这个redirect url要在keycloak中注册。
因为要输入认证信息,就不在postman中请求,而是直接到浏览器中访问即可。
- 发送该请求。
- 进入端点、认证信息、返回redirect。要注意的是前面说到这个auth端点会保存cookie,所以前面认证过后,再次发送对auth的请求,会根据cookie直接认证通过。
https://olimi.icu/?session_state=4595a24e-4c10-43cd-8e5c-e4e7e51b12b6&code=4bc51666-3927-4013-b16a-f2fdf6a16d83.4595a24e-4c10-43cd-8e5c-e4e7e51b12b6.f5635350-9dce-47aa-85cd-296e268daa40
- 拿着code,用code请求token。
返回
Implicit Flow 隐式流程(Legacy)
keycloak默认是关闭的,需要开启该模式。
然后同上。构造一个HTTP请求。
http://127.0.0.1:8080/realms/test-realm/protocol/openid-connect/auth?response_type=token&scope=openid&client_id=springsecurity&redirect_uri=http://olimi.icu
将response_type改为token即可。但只会返回一个access token.
返回:
总结
根据中间执行过程,可以看到标准流程,即授权码模式。中间只会暴露一个code给浏览器能看到。真正能访问资源的token是永远不会再浏览器中出现。而拿到code,换取token时一般还要验证client secret。这个也是只有client后端知道的。
相比标准流程,密码模式要求客户端请求token时直接使用用户的账户、密码来交换token,意味着客户端可以获取用户的账户密码,这是不安全的。标准模式虽然也会输入账户密码,但是是在客户端请求授权服务器的code的请求后,就跳转到授权服务器中进行验证,这个时候输入的账户密码是安全的。
然后客户凭证模式就跟用户认证没什么关系了。一般来说,什么什么权限都是分配给对应用户的。
除了基本必要的字段信息,协议中还包含非常多可选项,用于提供更多的保护和支持。比如关于state字段的安全性讨论:security - What attack does the "state" parameter in OpenID Connect server flow prevent? - Stack Overflow --- security - OpenID Connect 服务器流中的“state”参数可以防止什么攻击? - 堆栈溢出
扩展-PKCE
PKCE 不是客户端身份验证的一种形式,PKCE 也不是客户端密码或其他客户端身份验证的替代品。即使客户端使用客户端密码或其他形式的客户端身份验证(如 private_key_jwt),也建议使用 PKCE。注意:由于 PKCE 不能替代客户端身份验证,因此它不允许将公共客户端视为机密客户端。PKCE 最初旨在保护移动应用程序中的授权代码流,但其阻止授权代码注入的能力使其适用于每种类型的 OAuth 客户端,甚至是使用客户端身份验证的 Web 应用程序。
Postman中使用Authorization Code with PKCE即可。
大概过程就是选择一个安全的随机字符串,然后默认使用SHA-256计算其哈希值,作为code verifier发送给授权服务器。然后最后用code交换token时,将原字符串一起发送。这就保证了即使中间人盗取了code,也没用,因为没有这个原版的verifier字符串。也可选发送原版字符串而不是哈希值,但是就提高了被截取到这个字符串的可能,而截取哈希值是没办法反向获得原字符串。
postman测试过程(选择plain):请求code
用code请求token:
- 作者:Olimi
- 链接:https://olimi.icu/article/8c4cc4a4-19bc-4d8a-aff7-aff626cdde8d
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。