目录
对于认证的理解
- 传统session认证(认证通过信息写进session中、前后端不分离、模版引擎)
- 分布式session管理(如借助redis统一管理session)
- 单点登录、单点登出(需要
统一的认证中心来维护用户认证信息,如CAS)- 在CAS中就已经使用了浏览器端重定向、颁发ticket、验证ticket并返回用户信息等,
- 2类Session:CAS Server本身借助session来管理用户登录状态,而其他接入的Cas Client也借助各自的Session来管理各自的登录态。
- 现阶段单点登录:多个SPA前端
- OAuth三方登录集成(如对接微信登录)
- OAuth也是利用浏览器重定向,code换accessToken,accessToken换用户信息的套路(类似CAS)
- JWT的流行及无状态(使用JWT来代替session存储,契合OAuth2 Password)
- 账号密码暴露给客户端
- 客户端接入认证的逻辑总在变(如今天支持账号密码,明天支持手机号,后天支持微信登录,大后天…)
- 吊销token的有状态
- 身份云IDasS
- https://www.authing.co/
- https://auth0.com/
- Azure ADFS

JWT结构

https://jwt.io/
OAuth2.0
微信OAuth2.0登录
可以对比之前说的对接微信登录,那么:
- 微信 – 是认证中心(提供了OAuth协议)
- 微信 – 是资源(用户身份信息)的提供者
- 我们的接入系统 – 是接入微信登录的客户端,且是资源(用户身份)的索取者
换个角度,假如把微信也变成我们自己内部的一个系统,即统一认证中心(管理用户及认证,实现Oauth协议),那么:
- 我们的统一认证中心 – 提供了OAuth协议(用户管理、权限管理、认证UI管理等)
- 我们的资源系统 – 是资源(如后端API接口)的提供者,需要对accessToken进行认证和鉴权
- 我们的接入系统 – 是接入统一认证中心的客户端,且是资源(调用后端API)的索取者
OAuth2.0 授权模式
| 授权模式 | 说明 |
|---|---|
| 授权码模式(Authorization Code) 通过code换取oken |
较为常用,安全等级高,适用于存在Web后端的应用。 注: 通过code获取Token需要同时携带ClientId和ClientSecret, 所以该模式仅适用于可以安全存储ClientKey且不会泄露的应用(如后端服务), 而前端服务(SPA)、原生应用(桌面、移动端)都会暴露给具体用户,ClientKey有被用户拦截的风险, 下面提到的PKCE可适用于前端服务(SPA)、原生应用(桌面、移动端)。 |
| 授权码+PKCE模式(Authorization Code With PKCE) 通过code+code_verifier换取oken | 可适用于前端服务(SPA)、原生应用(桌面、移动端)。 |
| 客户端凭证模式(Client Credentials) 通过Cient凭证(ClientId和Secret)换取token | 适用于服务器间(M2M)通信。 |
| 刷新令牌模式(Refresh Token) 通过refresh_token换取新token | 根据之前授权通过时颁发的refresh_token获取新的access_token,避免用户多次重复登录(无感登录),提升用户体验。 |
| 设备码模式(Device Code) deviceCode+UserCode+VerficationUri 引导用户通过其他设备(如手机)进行认证授权 | 适用于可联网的无浏览器或输入受限的设备授权,例如智能电视、电子相册、打印机等。 |
直接返回token | 不推荐, 作为授权码模式的备用选项。 |
直接通过用户名密码换取token | 不推荐, 最不安全,客户端需要知道用户账号, 且客户端与认证方式强绑定,后续认证方式修改客户端也需要一起修改。 |
Authorization Code Flow

注: 把上图中的第5、6、7步删除,即对应Implicit授权模式。
Authorization Code Flow With PKCE

PKCE(Proof Key for Code Exchange)
通过一种密码学手段阻止CSRF和Authorization Code注入攻击,
确保恶意第三方即使截获Authorization Code,也无法向认证服务器交换Access Token,
同时不需要在客户端存储ClientKey,避免ClientKey被泄露。
code_verifier 验证码,随机字符串
code_challenge 挑战码,code_challenge = Base64(Sha256(code_verifier))
Client Credentials Flow

Resource Owner Password Flow

不推荐
1)账号密码暴露给Client端,不安全
2)客户端与认证方式绑定,认证逻辑修改则所有Client都需要修改
Device Authorization Flow



即将到来的OAuth2.1
- 仅保留
Authorization Code,Client Credentials,Refresh Token,Device Code授权模式 - 移除
Implicit和Password授权模式 - Authorization Code模式必须使用PKCE
- Bearer Token不可使用query参数形式
- Refresh Token for Public Client更严格(sender-contrained or one-time use)
OIDC(OpenID Connect) 1.0
- OAuth2.0的超集
- 身份层 – ID Token
- UserInfo Endpoint
- 单点登出

| OIDC补充协议 | 功能 |
|---|---|
| OpenID Connect Core 1.0 – UserInfo Endpoint | RP端向OP发出查询用户详细信息的请求 |
| OpenID Connect RP-Initiated Logout 1.0 | RP端向OP发出的初始登出请求(适用于单点登出SLO,end_session_endpoint),OP首先清除OP端的session, 然后向当前session下的其他已登录的RP触发Front-Channel或者Back-Channel登出请求, 最终可通过 post_logout_redirect_uri 重定向会当前RP
|
| OpenID Connect Front-Channel Logout 1.0 | 返回OP前端登出页面, 页面通过嵌入iframe(src= frontchannel_logout_uri),同时触发当前OP Session对应的多个Client的登出接口 |
| OpenID Connect Back-Channel Logout 1.0 | OP直接向当前OP Session对应的多个Client后端服务(不依赖User Agent,如不依赖浏览器)发送登出请求backchannel_logout_uri,且适用于User Agent被关闭的时候也可以退出登录。 |
| OpenID Connect Session Management 1.0 | RP端通过check_session_iframe 监听当前UserAgent中用户的登录状态,收到登录状态改变通知后RP应该清除自己的Session信息、页面跳转等。 |
OAuth2.0 & OIDC 1.0 授权模式选型

总结上图,关于OIDC的最优建议如下:
| 应用类型(即Client) | 选型建议 |
|---|---|
| SPA 如Vue、React等单页应用(前后端分离), 有专门的后端资源服务层(Resource Server)提供API服务 | PKCE + 授权码模式 即由前端接入认证,而后端仅接入鉴权 注: 如采用Nodej.js运行时环境并提供安全的后端存储,则可直接使用授权码模式。 |
| 原生应用Native(移动端应用、桌面应用) 如Android、Ios(前后端分离), 有专门的后端资源服务层(Resource Server)提供API服务 | PKCE + 授权码模式 即由前端接入认证,而后端仅接入鉴权 |
| WEB应用(即存在Web后端服务) 适用于获取第三方资源、集成第三方登录(如微信登录)、集成SSO | 授权码模式 接入Oauth后携带code回调到Web后端, 由后端获取AccessToken, 在后端携带AccessToken请求资源, 可使用session存储Token和User信息, 即使用session作为当前Web应用的身份验证机制 |
| 服务器间通信 无终端用户 | Client Credentials 模式 |
SSO

SLO

Spring Security OAuth2 及 Auhorization Server
Spring Seurity在2019年11月发布了OAuth 2.0 Migration Guide,
- 宣布禁用原spring-security-oauth,并将其合并到Spring Security 5.2+,
- 并且不再提供Authorization Server的支持。
后续在广大开发者的呼声下,在2020年4月Spring官方发布声明Announcing the Spring Authorization Server,
- 宣布启动由社区主导的spring-authorization-server项目,专注于OAuth2 Authorization Server实现,
- 目前最新版本为spring-authorization-server:0.2.2(发布于2022-01-27)。
截止到今天(2022-04-21):
- Spring Security 5.6.3- OAuth2
- OAuth2 Log In
- OAuth2 Client
- OAuth2 Resource Server
- Spring社区版 – OAuth2 Authorization Server 0.2.3
如上几大模块组成了Spring Security对OAuth2生态的最新支持。
Security架构

比如认证相关的Filter,其总体结构如下:
- AuthenticationProcessingFilter – 认证过滤器
- AuthenticationManager -> ProviderManager – 认证管理器
- AuthenticationProvider – 具体的认证逻辑实现
- AuthenticationSuccessHandler – 认证成功处理器
- AuthenticationFailureHandler – 认证失败处理器
- AuthenticationManager -> ProviderManager – 认证管理器
Authorization Server整体调用层次:
- EndpointFilter
- AuthenticationConverter – 转换request为Authentication
- AuthenticationManager -> ProviderManager – 认证管理器
- AuthenticationProvider.authenticate(authentication)
- AuthenticaionSuceessHandler.onAuthenticationSuccess
- AuthenticaionFailureHandler.onAuthenticationFailure
Authorization Server核心FIlter
| 核心FIlter | endpoint | 说明 |
|---|---|---|
| OAuth2AuthorizationEndpointFilter | GET|POST /oauth2/authorize | 授权端点,即RP跳转到OP的认证入口, 且EU认证通过后,OP重定向回RP,且附加code参数 |
| OAuth2ClientAuthenticationFilter | POST /oauth2/token|introspect|revoke | 即RP向OP发送获取token请求、检查token、吊销token时,OP端提供的认证逻辑 |
| OAuth2TokenEndpointFilter | POST /oauth2/token | Token端点,RP向OP请求Token(通过code换token、执行refresh_token流程) |
| OAuth2TokenIntrospectionEndpointFilter | POST /oauth2/introspect | 校验Token端点,RP请求OP检测token有效性 |
| OAuth2TokenRevocationEndpointFilter | POST /oauth2/revoke | 吊销Token端点,RP请求OP吊销token |
| OidcProviderConfigurationEndpointFilter | GET /.well-known/openid-configuration | OIDC协议发现端点 |
| OidcUserInfoEndpointFilter | GET /userinfo | 用户信息端点,提供用户信息查询 |
| OidcClientRegistrationEndpointFilter | POST /connect/register | 客户端信息注册端点(暂未使用) |
Spring Security OAuth2支持情况
查看Spring Security相关源码,可以发现如上提到的核心功能支持情况如下表:
| 功能 | Authorization Server | OAuth2 Client |
|---|---|---|
| Authorization Code Flow with PKCE 授权码Code流程 + PKCE |
✔️ | ✔️ |
| Refresh Token without client_secret PKCE后续无client_secret刷新token |
❌ | ❌ |
| Refresh Token Retotation(Reuse) 令牌轮换 |
✔️ | ✔️ |
| OAuth2 SSO 单点登录 |
✔️ | ✔️ |
| OIDC end_session_endpoint 单点登出 |
❌ | ✔️ |
所以以上表格中未实现的,也就是需要扩展的功能。
扩展后的Authorization Code + PKCE时序图

认证架构演进
前后端不分离 + RPC

前后端分离

前后端分离 + 应用网关

Istio对Resource Server的支持

RequestAuthentication 整体配置结构如下:
namespace策略生效的命名空间selector -> matchLables指定策略作用的目标workloadjwtRulesjwt验证规则的配置issuerjwt的发布者audiences受众,即jwt的接受者jwksUri | jwksjwks(公钥),即用来验证jwt签名的公钥,jwksUri和jwks二选一fromHeadersjwt在请求头的位置nameheader名称prefixtoken前缀,如”Bearer “
fromParamsjwt在query参数中的参数名称outputPayloadToHeader指定jwt验证通过后的payload传递给哪个请求headerforwardOriginalToken是否保留原始token,默认false
jwtRules概括起来分为如下几类配置:
- jwt token的位置(jwtRules.fromHeaders, jwtRules.fromParams)
- jwt.payload等相关属性(jwtRules.issuer, jwtRules.audiences)、request
- jwt公钥,即JWKS(JSON Web Key Set),用来验证jwt的签名(jwtRules.jwksUri, jwtRules.jwks)
- 是否保留jwt(jwtRules.outputPayloadToHeader, jwtRules.forwardOriginalToken)
RequestAuthentication 配置示例
apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: httpbin namespace: foo spec: # worload的label选择器 selector: matchLabels: app: httpbin # jwt验证规则 jwtRules: # jwt的发布者 - issuer: "issuer-foo" # 受众,即jwt的接受者 audiences: - bookstore_android.apps.example.com bookstore_web.apps.example.com # jwks(公钥)uri发现地址,即用来验证jwt签名的公钥 jwksUri: https://example.com/.well-known/jwks.json # jwks(此属性与jwksUri需二选一) jwks: "jwks json" # jwt在请求头的位置 fromHeaders: - name: x-jwt-assertion prefix: "Bearer " # jwt在query参数中的参数名称 fromParams: - "my_token" # 指定jwt验证通过后的payload传递给哪个请求header # 传递格式:base64_encoded(jwt_payload_in_JSON) outputPayloadToHeader: payload_header # 是否保留原始token(若保留则继续传递token到upstream请求),默认false forwardOriginalToken: false
注:
- jwt验证通过,则正常接受请求
- jwt验证失败,则拒绝请求
jwt为空,默认接受请求(需通过设置Authoriation policies来拒绝不带token的请求)- 支持多个不同位置的jwt token,
- 但仅支持一个有效的token(多个有效的token会导致输出的principal不确定)
AuthorizationPolicy 其整体结构如下:
namespace策略生效的命名空间(即目标workload所属的namespace)selector -> matchLables指定策略作用的目标workloadactions: CUSTOM | DENY | ALLOW是否允许请求rules指定触发action的条件(即满足rules定义的请求,则执行action)from -> source[]指定请求来源(source满足的条件),为空则表示全部来源
pincipals(workload身份)、requestPrincipals(用户身份)、namespaces、ipBlocks、remoteIpBlocks及相应not属性to -> operation[]指定请求的操作(request满足的条件),为空则表示所有操作
hosts、ports、methods、paths及相应not属性when指定额外的附件条件(key对应Istio属性)key -> values[] | notValues[]
request.headers[header_name]、
request.auth.[principal | audiences | presenter | claims[claimName] ]、
source.[ip | namespace | principal ]、remote.ip、
destination.[ip | port]、 connection.sni
具体配置示例:
# 作用目标: workload(namespace=foo, label(app=httpbin, version=v1)) # 动作action: 允许访问ALLOW # 来源rules.from.source: principals=="cluster.local/ns/default/sa/sleep" or namepsace=="dev" # 操作rules.to.operation:method=="GET" # 条件rules.when: request.auth.claims[iss]=="https://accounts.google.com" # 即[仅允许][cluster.local/ns/default/sa/sleep身份(PeerAuthentication mTls验证通过)、或者dev命名空间]的workload访问[httpbin:v1服务的GET请求] # 且[用户RequestAuthentication JWT验证通过、且jwt.claims[iss]=="https://accounts.google.com"] apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: ALLOW rules: - from: - source: # 指定来源workload身份 # principals: ["*"]则表示需要PeerAuthentication mtls验证通过 principals: ["cluster.local/ns/default/sa/sleep"] - source: # 指定来源namespace namespaces: ["dev"] to: - operation: # 执行GET请求 methods: ["GET"] when: # 指定用户jwt需验证通过,且iss为指定值 - key: request.auth.claims[iss] values: ["https://accounts.google.com"] # [拒绝][不属于foo命名空间的服务]访问[httpbin:v1服务] apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin-deny namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: DENY rules: - from: - source: # 不是来自于foo命名空间 notNamespaces: ["foo"] # 访问非/healthz的请求,均需要JWT验证通过, # 而访问/healthz的请求,可无需JWT验证(即请求未携带JWT令牌、或者携带的JWT令牌为空) apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: disable-jwt-for-healthz namespace: default spec: selector: matchLabels: app: products action: ALLOW rules: - to: - operation: # 访问除了/healthz以外的path notPaths: ["/healthz"] from: - source: # 需要RequestAuthentication JWT验证通过 requestPrincipals: ["*"]
APISIX对Resource Server的支持


附录
附录A – 微信OAuth端点
并非标准OAuth2 Authorization Code模式,例如:
- appid, secret
- token_endpoint不唯一
- https://open.weixin..com/connect/qrconnect
- ?appid=APPID
- &redirect_uri=REDIRECT_URI
- &response_type=code
- &scope=snsapi_login|snsapi_userinfo
- &state=STATE
- #wechat_redirect
- https://api.weixin..com/sns/oauth2/access_token
- ?appid=APPID
- &secret=SECRET
- &code=CODE
- &grant_type=authorization_code
- https://api.weixin..com/sns/oauth2/refresh_token
- ?appid=APPID
- &grant_type=refresh_token
- &refresh_token=REFRESH_TOKEN
- https://api.weixin..com/sns/userinfo
- ?access_token=ACCESS_TOKEN
- &openid=OPENID
附录B – OAuth & OIDC术语
OIDC及OAuth核心术语:
| OIDC | OAuth | 说明 |
|---|---|---|
| EU End User |
Resource Owner | 拥有资源的用户 |
| RP Relying Party |
Client Third-party application |
用来代指OAuth2中的受信任的客户端(Web应用、SPA、移动端应用…), 即接入认证中心(OP、Authorization Server)登录跳转且并且获取Token(IdToken、AccessToken), 可通过IdToken获取用户身份信息, 然后携带AccessToken访问资源(Resource Server、Userinfo Endpoint) |
| OP OpenID Provider | Auhtorization Server | 为用户(EU、Resource Owner)提供认证的服务(如微信授权平台、Github授权平台、自建用户中心等), 用来为应用客户端(RP、Client)提供用户(EU、Resource Owner)的身份认证信息 |
| Resource Server | Resource Server | 资源服务器, 即提供资源API且需要对(受保护的)资源访问进行鉴权的服务, Cilent端携带AccessToken访问ResourceServer, 然后由Resource Server对此AccessToken进行鉴权 |
| Endpoint | Endpoint | 端点,即提供的API接口 |
| ID Token | 无 | 身份令牌(JWT), 用于认证,标识用户身份已经认证,可用于获取用户身份信息(如用户名、头像等)。 |
| Access Token | Access Token | 访问令牌(JWT), 用于鉴权,适用于API的访问鉴权。 |
| User Agent | User Agent | 应用执行端, 如浏览器,手机端,即RP的执行端 |
附录C – OAuth & OIDC常用端点
OIDC和OAuth的常用端点(Endpoint,即提供的API接口)
| endpoint | 提供方 | 调用方 | 认证 | 格式 | desc |
|---|---|---|---|---|---|
| authoriztion_endpoint | OP | RP | 无 | Code Flow模式: GET authorization_endpoint ?response_type=code &redirect_uri={redirect_uri} &client_id={client_id} &state={random_str} &nonce={another_random_str} &scope=openid profile email PKCE + Code Flow模式 | 进入OP登录授权界面 |
| redirect_uri | RP | OP | 无 | GET redirect_uri ?code={code} &state={random_str} | 在OP登录成功后,将code回调给RP |
| token_endpoint | OP | RP | client_secret_post client_secret_basic | Code Flow模式: POST token_endpoint ?grant_type=authorization_code &code={code} &redirect_uri={redirect_uri} &client_id={client_id} &client_secret={client_secret} PKCE + Code Flow模式 Refresh Token模式: | 通过code获取access_token、id_token,
根据refresh_token获取新的access_token |
| userinfo_endpoint | OP | RP | Bearer {access_token} | GET userinfo_endpoint | 根据acces_token获取用户信息 |
| introspection_endpoint | OP | Resource Server | client_secret_post client_secret_basic | POST introspection_endpoint ?token={your_token} | 用于验证access_token是否有效, 通过返回结果中的active进行标识 |
| revocation_endpoint | OP | RP | client_secret_post client_secret_basic | POST revocation_endpoint ?token={your_token} &token_type_hint={} &token_type_hint=access_token | 撤销token、access_token, 若撤销refresh_token,则此refresh_token关联的access_token也会被撤销 |
| end_session_endpoint | OP | RP | 无 | GET end_session_point ?id_token_hint={id_token_issued_to_client} &post_logout_redirect_uri={post_logout_redirect_uri} &state={random_str} | 结束浏览器中OP的session会话, 并触发后续的多RP的Front-Channel或Back-Channel登出, 最后可配合post_logout_redirect_uri 重定向回RP页面 |
| post_logout_redirect_uri | RP | OP | 无 | GET post_logout_redirect_uri | OP登出成功后会重定向回RP页面, 或对应于RP登录界面、登出状态展示页面等 |
| check_session_iframe | OP | RP | 无 | RP iframe.src=check_session_iframe | Session Management,检查当前RP的登录状态 |
| frontchannel_logout_uri | RP | OP iframe | 无 | GET fontchannel_logout_uri ?iss={issureId} &sid={idToken.sid} |
RP前端实现的登出处理页面, 由OP Front-Channel模式下返回的Html iframe调用 |
| backchannel_logout_uri | RP | OP | 无 | POST backchannel_logout_uri ?logout_token={logout_token like idToken} |
RP后端实现的后端登出接口, RP后端需验证logout_token并根据logout_token找出并清除后端的对应的session信息 |
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177329.html原文链接:https://javaforall.net
