目录

协议

五种认证方式

  • Authorization Code 授权码模式:认证服务返回授权码,后端用clientid和密钥向认证服务证明身份,使用授权码换取id token 和/或 access token。本模式的好处是由后端请求token,不会将敏感信息暴露在浏览器。本模式允许使用refreshToken去维持长时间的登录状态。使用此模式的客户端必须有后端参与,能够保障客户端密钥的安全性。此模式从authorization接口获取授权码,从token接口获取令牌。

  • Implict 简化模式:校验跳转URI验证客户端身份之后,直接发放token。通常用于纯客户端应用,如单页应用javascript客户端。因为没有后端参与,密钥存放在前端是不安全的。由于安全校验较宽松,本模式不允许使用refreshToken来长时间维持登录状态。本模式的所有token从authorization接口获取。

  • Hybrid 混合流程:混合流程顾名思义组合使用了授权码模式+简化模式。前端请求授权服务器返回授权码+id_token,这样前端立刻可以使用用户的基本信息;后续请求后端使用授权码+客户端密钥获取access_token。本模式能够使用refreshToken来长时间维持登录状态。使用本模式必须有后端参与保证客户端密钥的安全性。混合模式极少使用,除非你的确需要使用它的某些特性(如一次请求获取授权码和用户资料),一般最常见的还是授权码模式。

  • Resource Owner Password Credential 用户名密码模式:一般用于无用户交互场景,或者第三方对接(如对接微信登录,实际登录界面就变成了微信的界面,如果不希望让客户扫了微信之后再跑你们系统登录一遍,就可以在后端用此模式静默登录接上自家的sso即可)

  • Client Credential 客户端密钥模式:仅需要约定密钥,仅用于完全信任的内部系统

认证方式特点对比

特点 授权码模式 简化模式 混合模式
所有token从Authorization接口返回 No Yes Yes
所有token从Token接口返回 Yes No No
所有tokens不暴露在浏览器 Yes No No
能够验证客户端密钥 Yes No Yes
能够使用刷新令牌 Yes No Yes
仅需一次请求 No Yes No
大部分请求由后端进行 Yes No 可变

支持返回类型对比

返回类型 认证模式 说明
code Authorization Code Flow 仅返回授权码
id_token Implicit Flow 返回身份令牌
id_token token Implicit Flow 返回身份令牌、通行令牌
code id_token Hybrid Flow 返回授权码、身份令牌
code token Hybrid Flow 返回授权码、通行令牌
code id_token token Hybrid Flow 返回授权码、身份令牌、通行令牌

授权码模式解析

相对来说,授权码模式还是用的最多的,我们详细解读一下本模式的协议内容。

授权时序图

sequenceDiagram
用户->>客户端: 请求受保护资源
客户端->>认证服务: 准备入参,发起认证请求
认证服务->>认证服务: 认证用户
认证服务->>用户: 是否同意授权
认证服务->>客户端: 发放授权码(前端进行)
客户端->>认证服务: 使用授权码请求token(后端进行)
认证服务->>认证服务: 校验客户端密钥,校验授权码
认证服务->>客户端: 发放身份令牌、通行令牌(后端进行)
客户端->>客户端: 校验身份令牌,获取用户标识

认证请求

认证接口必须同时支持GET和POST两种请求方式。如果使用GET方法,客户端必须使用URI Query传递参数,如果使用POST方法,客户端必须使用Form传递参数。

参数定义

  • scope:授权范围,必填。必须包含openid。
  • response_type:返回类型,必填。定义了认证服务返回哪些参数。对于授权码模式,本参数只能是code。
  • client_id:客户端id,必填。
  • redirect_uri:跳转地址,必填。授权码生成之后,认证服务会带着授权码和其他参数回跳到此地址。此地址要求使用https。如果使用http,则客户端类型必须是confidential。
  • state:状态字段,推荐填写。一般用于客户端与认证服务比对此字段,来防跨站伪造攻击,同时state也可以存放状态信息,如发起认证时的页面地址,用于认证完成后回到原始页面。
  • 其他:略。上面五个是和OAuth2.0一样的参数,oidc还定义了一些扩展参数,用的很少,不是很懂,感兴趣的自己去看协议。

请求报文示例

HTTP/1.1 302 Found
Location: https://server.example.com/authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

认证请求校验

  • 必填校验
  • response_type必须为code
  • scope必填,必须包含openid

认证终端用户

  • 下面两种情况认证服务必须认证用户

    • 用户尚未认证
    • 认证请求包含参数prompt=login,即使用户已经认证过也需要重新认证
    • 认证请求包含参数prompt=none,然后用户尚未被认证,则需要返回错误信息

认证服务必须想办法防止过程中的跨站伪造攻击和点击劫持攻击。

获取终端用户授权/同意

终端用户通过认证之后,认证服务必须与终端用户交互,询问用户是否同意对客户端的授权。

认证响应

成功响应

使用 application/x-www-form-urlencoded格式返回结果

例如:

 HTTP/1.1 302 Found
Location: https://client.example.org/cb?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj

失败响应

错误代码包括这些

oauth2.0定义的响应代码

  • invalid_request:非法请求,未提供必填参数,参数非法等情况
  • unauthorized_client:客户端未授权
  • access_denied:用户无权限
  • unsupported_response_type
  • invalid_scope:非法的scope参数
  • server_error
  • temporarily_unavailable

    另外oidc还扩展了一些响应代码,不常见,略

例如:

  HTTP/1.1 302 Found
Location: https://client.example.org/cb?
error=invalid_request
&error_description=
Unsupported%20response_type%20value
&state=af0ifjsldkj

客户端校验授权码

协议规定客户端必须校验授权码的正确性

源码解析

从AuthorizeEndpoint的ProcessAsync方法作为入口开始认证接口的源码解析。

  • 判断请求方式是GET还是POST,获取入参,如果是其他请求方式415状态码
  • 从session中获取user
  • 入参和user作为入参,调用父类ProcessAuthorizeRequestAsync方法
    public override async Task<IEndpointResult> ProcessAsync(HttpContext context)
{
Logger.LogDebug("Start authorize request"); NameValueCollection values; if (HttpMethods.IsGet(context.Request.Method))
{
values = context.Request.Query.AsNameValueCollection();
}
else if (HttpMethods.IsPost(context.Request.Method))
{
if (!context.Request.HasFormContentType)
{
return new StatusCodeResult(HttpStatusCode.UnsupportedMediaType);
} values = context.Request.Form.AsNameValueCollection();
}
else
{
return new StatusCodeResult(HttpStatusCode.MethodNotAllowed);
} var user = await UserSession.GetUserAsync();
var result = await ProcessAuthorizeRequestAsync(values, user, null); Logger.LogTrace("End authorize request. result type: {0}", result?.GetType().ToString() ?? "-none-"); return result;
}

认证站点如果cookie中存在当前会话信息,则直接返回用户信息,否则调用cookie架构的认证方法,会跳转到登录页面。

public virtual async Task<ClaimsPrincipal> GetUserAsync()
{
await AuthenticateAsync(); return Principal;
} protected virtual async Task AuthenticateAsync()
{
if (Principal == null || Properties == null)
{
var scheme = await GetCookieSchemeAsync(); var handler = await Handlers.GetHandlerAsync(HttpContext, scheme);
if (handler == null)
{
throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
} var result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
{
Principal = result.Principal;
Properties = result.Properties;
}
}
}

认证请求处理流程大致分为三步

  • AuthorizeRequestValidator校验所有参数
  • 认证接口consent入参为null,不需要处理用户交互判断
  • 生成返回报文
 internal async Task<IEndpointResult> ProcessAuthorizeRequestAsync(NameValueCollection parameters, ClaimsPrincipal user, ConsentResponse consent)
{
if (user != null)
{
Logger.LogDebug("User in authorize request: {subjectId}", user.GetSubjectId());
}
else
{
Logger.LogDebug("No user present in authorize request");
} // validate request
var result = await _validator.ValidateAsync(parameters, user);
if (result.IsError)
{
return await CreateErrorResultAsync(
"Request validation failed",
result.ValidatedRequest,
result.Error,
result.ErrorDescription);
} var request = result.ValidatedRequest;
LogRequest(request); // determine user interaction
var interactionResult = await _interactionGenerator.ProcessInteractionAsync(request, consent);
if (interactionResult.IsError)
{
return await CreateErrorResultAsync("Interaction generator error", request, interactionResult.Error, interactionResult.ErrorDescription, false);
}
if (interactionResult.IsLogin)
{
return new LoginPageResult(request);
}
if (interactionResult.IsConsent)
{
return new ConsentPageResult(request);
}
if (interactionResult.IsRedirect)
{
return new CustomRedirectResult(request, interactionResult.RedirectUrl);
} var response = await _authorizeResponseGenerator.CreateResponseAsync(request); await RaiseResponseEventAsync(response); LogResponse(response); return new AuthorizeResult(response);
}

生成返回信息

此处只有AuthorizationCode、Implicit、Hybrid三种授权类型的判断,用户名密码、客户端密钥模式不能使用authorize接口。

  public virtual async Task<AuthorizeResponse> CreateResponseAsync(ValidatedAuthorizeRequest request)
{
if (request.GrantType == GrantType.AuthorizationCode)
{
return await CreateCodeFlowResponseAsync(request);
}
if (request.GrantType == GrantType.Implicit)
{
return await CreateImplicitFlowResponseAsync(request);
}
if (request.GrantType == GrantType.Hybrid)
{
return await CreateHybridFlowResponseAsync(request);
} Logger.LogError("Unsupported grant type: " + request.GrantType);
throw new InvalidOperationException("invalid grant type: " + request.GrantType);
}
  • 如果state字段不为空,使用加密算法得到state的hash值
  • 构建AuthorizationCode对象,存放在store中,store是idsv4用于持久化的对象,默认实现存储在内存中,可以对可插拔服务进行注入替换,实现数据保存在在mysql、redis等流行存储中
  • 将授权码对象的id返回
 protected virtual async Task<AuthorizeResponse> CreateCodeFlowResponseAsync(ValidatedAuthorizeRequest request)
{
Logger.LogDebug("Creating Authorization Code Flow response."); var code = await CreateCodeAsync(request);
var id = await AuthorizationCodeStore.StoreAuthorizationCodeAsync(code); var response = new AuthorizeResponse
{
Request = request,
Code = id,
SessionState = request.GenerateSessionStateValue()
}; return response;
} protected virtual async Task<AuthorizationCode> CreateCodeAsync(ValidatedAuthorizeRequest request)
{
string stateHash = null;
if (request.State.IsPresent())
{
var credential = await KeyMaterialService.GetSigningCredentialsAsync();
if (credential == null)
{
throw new InvalidOperationException("No signing credential is configured.");
} var algorithm = credential.Algorithm;
stateHash = CryptoHelper.CreateHashClaimValue(request.State, algorithm);
} var code = new AuthorizationCode
{
CreationTime = Clock.UtcNow.UtcDateTime,
ClientId = request.Client.ClientId,
Lifetime = request.Client.AuthorizationCodeLifetime,
Subject = request.Subject,
SessionId = request.SessionId,
CodeChallenge = request.CodeChallenge.Sha256(),
CodeChallengeMethod = request.CodeChallengeMethod, IsOpenId = request.IsOpenIdRequest,
RequestedScopes = request.ValidatedScopes.GrantedResources.ToScopeNames(),
RedirectUri = request.RedirectUri,
Nonce = request.Nonce,
StateHash = stateHash, WasConsentShown = request.WasConsentShown
}; return code;
}

返回结果

  • 如果ResponseMode等于Query或者Fragment,将授权码code及其他信息拼装到Uri,返回302重定向请求

    例子:
302 https://mysite.com?code=xxxxx&state=xxx
  • 如果是FormPost方式,会生成一段脚本返回到客户端。窗口加载会触发form表单提交,将code、state等信息包裹在隐藏字段里提交到配置的rediret_uri。
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<base target='_self'/>
</head>
<body>
<form method='post' action='https://mysite.com'>
<input type='hidden' name='code' value='xxx' />
<input type='hidden' name='state' value='xxx' />
<noscript>
<button>Click to continue</button>
</noscript>
</form>
<script>window.addEventListener('load', function(){document.forms[0].submit();});</script>
</body>
</html>
private async Task RenderAuthorizeResponseAsync(HttpContext context)
{
if (Response.Request.ResponseMode == OidcConstants.ResponseModes.Query ||
Response.Request.ResponseMode == OidcConstants.ResponseModes.Fragment)
{
context.Response.SetNoCache();
context.Response.Redirect(BuildRedirectUri());
}
else if (Response.Request.ResponseMode == OidcConstants.ResponseModes.FormPost)
{
context.Response.SetNoCache();
AddSecurityHeaders(context);
await context.Response.WriteHtmlAsync(GetFormPostHtml());
}
else
{
//_logger.LogError("Unsupported response mode.");
throw new InvalidOperationException("Unsupported response mode");
}
}

客户端在回调地址接收code,即可向token接口换取token。

其他

简单看一下简化流程和混合流程是怎么创建返回报文的。

简化流程生成返回报文

  • 如果返回类型包含token,生成通行令牌
  • 如果返回类型包含id_token,生成身份令牌

可以看到,简化流程的所有token都是由authorization接口返回的,一次请求返回所有token。

protected virtual async Task<AuthorizeResponse> CreateImplicitFlowResponseAsync(ValidatedAuthorizeRequest request, string authorizationCode = null)
{
Logger.LogDebug("Creating Implicit Flow response."); string accessTokenValue = null;
int accessTokenLifetime = 0; var responseTypes = request.ResponseType.FromSpaceSeparatedString(); if (responseTypes.Contains(OidcConstants.ResponseTypes.Token))
{
var tokenRequest = new TokenCreationRequest
{
Subject = request.Subject,
Resources = request.ValidatedScopes.GrantedResources, ValidatedRequest = request
}; var accessToken = await TokenService.CreateAccessTokenAsync(tokenRequest);
accessTokenLifetime = accessToken.Lifetime; accessTokenValue = await TokenService.CreateSecurityTokenAsync(accessToken);
} string jwt = null;
if (responseTypes.Contains(OidcConstants.ResponseTypes.IdToken))
{
string stateHash = null;
if (request.State.IsPresent())
{
var credential = await KeyMaterialService.GetSigningCredentialsAsync();
if (credential == null)
{
throw new InvalidOperationException("No signing credential is configured.");
} var algorithm = credential.Algorithm;
stateHash = CryptoHelper.CreateHashClaimValue(request.State, algorithm);
} var tokenRequest = new TokenCreationRequest
{
ValidatedRequest = request,
Subject = request.Subject,
Resources = request.ValidatedScopes.GrantedResources,
Nonce = request.Raw.Get(OidcConstants.AuthorizeRequest.Nonce),
IncludeAllIdentityClaims = !request.AccessTokenRequested,
AccessTokenToHash = accessTokenValue,
AuthorizationCodeToHash = authorizationCode,
StateHash = stateHash
}; var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest);
jwt = await TokenService.CreateSecurityTokenAsync(idToken);
} var response = new AuthorizeResponse
{
Request = request,
AccessToken = accessTokenValue,
AccessTokenLifetime = accessTokenLifetime,
IdentityToken = jwt,
SessionState = request.GenerateSessionStateValue()
}; return response;
}

混合流程生成返回报文

这段代码充分体现了它为啥叫混合流程,把生成授权码的方法调一遍,再把简化流程的方法调一遍,code和token可以一起返回。

protected virtual async Task<AuthorizeResponse> CreateHybridFlowResponseAsync(ValidatedAuthorizeRequest request)
{
Logger.LogDebug("Creating Hybrid Flow response."); var code = await CreateCodeAsync(request);
var id = await AuthorizationCodeStore.StoreAuthorizationCodeAsync(code); var response = await CreateImplicitFlowResponseAsync(request, id);
response.Code = id; return response;
}

identityserver4源码解析_3_认证接口的更多相关文章

  1. identityserver4源码解析_2_元数据接口

    目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...

  2. IdentityServer4源码解析_4_令牌发放接口

    目录 identityserver4源码解析_1_项目结构 identityserver4源码解析_2_元数据接口 identityserver4源码解析_3_认证接口 identityserver4 ...

  3. IdentityServer4源码解析_5_查询用户信息接口

    协议简析 UserInfo接口是OAuth2.0中规定的需要认证访问的接口,可以返回认证用户的声明信息.请求UserInfo接口需要使用通行令牌.响应报文通常是json数据格式,包含了一组claim键 ...

  4. IdentityServer4源码解析_1_项目结构

    目录 IdentityServer4源码解析_1_项目结构 IdentityServer4源码解析_2_元数据接口 IdentityServer4源码解析_3_认证接口 IdentityServer4 ...

  5. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  6. Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制

    在上篇我们对Django原生View源码进行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html 在前后端分离项目中前面我们也提到了各种认证需要 ...

  7. Java基础——集合源码解析 List List 接口

    今天我们来学习集合的第一大体系 List. List 是一个接口,定义了一组元素是有序的.可重复的集合. List 继承自 Collection,较之 Collection,List 还添加了以下操作 ...

  8. .Net Core 认证系统之Cookie认证源码解析

    接着上文.Net Core 认证系统源码解析,Cookie认证算是常用的认证模式,但是目前主流都是前后端分离,有点鸡肋但是,不考虑移动端的站点或者纯管理后台网站可以使用这种认证方式.注意:基于浏览器且 ...

  9. 【.NET Core项目实战-统一认证平台】第八章 授权篇-IdentityServer4源码分析

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上实现客户端自定义限流功能,基本完成了关于网关的一些自定义扩展需求,后面几篇将介绍基于IdentityServer ...

随机推荐

  1. vue子组件使用自定义事件向父组件传递数据

    使用v-on绑定自定义事件可以让子组件向父组件传递数据,用到了this.$emit(‘自定义的事件名称’,传递给父组件的数据) <!DOCTYPE html> <html lang= ...

  2. react动态添加多个输入框

    let obj = {} result.forEach(item =>{ obj[item.eleId] = item }) setFieldsValue(obj)

  3. ERROR 1129 (00000) Host ‘XXXXXX’ is blocked because of many connection errors; unblock with ‘mysqlad

    1.今天早上由于公司网络带宽达到上限,导致多台web服务器连接mysql服务器超时.后来情况好转后,连接数据库服务器出现如下错误. Host '*' is blocked because of man ...

  4. Jackie's blog

    介绍使用winmm.h进行音频流的获取   首先需要包含以下引用对象 #include <Windows.h>#include "mmsystem.h"#pragma ...

  5. MIZ702N开发环境的准备1

    前言 最近由于工作需要开始接手基于MIZ702的硬件平台的Linux的开发,仔细想想,工作这么久,这好像还是我第一次接手嵌入式Liunx相关的工作.这几天拿到开发板,开始了阅读文档.安装Ubuntu虚 ...

  6. 初识SpringIOC

    初识SpringIOC 简介 IOC(Inversion of Control)控制反转.指的是获取对象方式由原来主动获取,到被动接收的转变.在Spring中,IOC就是工厂模式解耦,是Srping框 ...

  7. 提高开发效率之VS Code基础配置篇

    背景 之前一直是只用WebStorm作为IDE来编写代码,但是由于: 手中的这台Mac接了两个显示器以后,使用WebStorm会有卡顿. WebStorm需要付费(虽然可以通过某方法和谐). 所以需要 ...

  8. 学习经典算法—JavaScript篇(一)排序算法

    前端攻城狮--学习常用的排序算法 一.冒泡排序 优点: 所有排序中最简单的,易于理解: 缺点: 时间复杂度O(n^2),平均来说是最差的一种排序方式: 因为在默认情况下,对于已经排好序的部分,此排序任 ...

  9. 网址封锁的几种方法 公司把 pan.baidu.com 封了 研究实现原理

    HTTP 和 HTTPS 协议HTTP 协议在 头部会发送 host 就是要访问的域名,可以用来被检测. HTTPS 协议虽然会加密全部通讯,但是在握手之前还是明文传输.有证书特证可被检测. 1, D ...

  10. 《ASP.NET Core 3框架揭秘》博文汇总

    在过去一段时间内,写了一系列关于ASP.NET Core 3相关的文章,其中绝大部分来源于即将出版的<ASP.NET Core 3框架揭秘>(博文只能算是"初稿",与书 ...