从零搭建一个IdentityServer——初识OpenIDConnect
7m鱼 人气:0 上一篇文章实现了IdentityServer4与Asp.net core Identity的集成,可以使用通过identity注册功能添加的用户,以Password的方式获取Access token,但是无论是Client Credentials还是Password流程它都是OAuth2.0的流程,本篇文章就来先介绍一下关于OpenIDConnect的基本概念和用法。
本文有以下内容:
- OpenIDConnect介绍及基本概念
- OIDC授权码流程及实现
- OIDC隐式流程及实现
- 小结
OpenIDConnect介绍及基本概念
根据OpenIDConnect的定义简单来说,OpenIDConnect=(Identity, Authentication)+Oauth2.0(来自:https://openid.net/connect/faq/)
另外从下面的回答可以看出OAuth2.0是一个身份验证/授权框架,而OpenID Connect基于这些提供了身份标识功能,身份标识就是“是谁”的 问题。
![](https://img.qb5200.com/download-x/20210201/26618.png)
至于oidc如何实现的,详情可以查看文档:https://openid.net/specs/openid-connect-core-1_0.html,从文档中可以找到这样几个关键词:ID Token、Authentication、Authentication Request、Authorization Code Flow、Implicit Flow、Hybrid Flow、Response_Type、Authorization Endpoint、Token Endpoint。
以及两个表,OIDC身份验证流程表:
![](https://img.qb5200.com/download-x/20210201/26619.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\331791bb572e4e4697174ede77140e70\clipboard.png)
OIDC身份验证请求对应的相应类型(Response_Type)表:
![](https://img.qb5200.com/download-x/20210201/26621.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\20c508e77e1d4692b54aaa79931f7664\clipboard.png)
还有一个流程图:
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\e295e8b6b1c94ae6bc64648d6e5e2590\clipboard.png)
![](https://img.qb5200.com/download-x/20210201/26624.png)
从以上关键词可以获得下面的信息:
- ID Token:是一个包含特定声明(Claim)的jwt,特定的声明指的是身份验证服务器验证终端用户时候产生的供客户端使用的信息,如发行人(issuer)、最终用户标识(sub)、客户端id(aud)、过期时间(exp)、Token的发布时间(iat)、用户身份验证时间(auth_time)等,另外也可以包含其它的声明。ID Token由身份验证服务器(IdentityServer4,OP)颁发,交由客户端(RP)进行验证。
- Authentication:由IdentityServer4(OP)提供的身份验证(登录),最终用户通过IdentityServer4(OP)的身份验证(登录)后,就可以发起Authentication Request,或者说如果在发起Authentication Request时用户未进行身份验证时将重定向到身份验证界面进行身份验证。
- Authentication Request:向IdentityServer4(OP)的授权终结点发起的,用于获取ID Token、授权码(Authorization Code)甚至是访问Token(Access Token)的请求。
- Authorization Code Flow、Implicit Flow、Hybrid Flow:Authentication Request的三种不同请求流程。
- Response_Type:Authentication Request的参数之一,根据设定该参数来决定使用哪一种请求流程。
- Authorization Endpoint:授权终结点,用于接收Authentication Request。
- Token Endpoint:令牌终结点,用于接收访问令牌(Access Token)获取请求。
从两个表格可以知道:三种身份验证流程的特性,如各Token从哪个终结点返回、是否向用户代理(如浏览器)透漏Token、是否支持刷新Token等。三种身份验证流程通过指定Response_Type来决定,如参数值为code时进行授权码流程,Id_token以及id_token token时进行隐式流程,包含code以及token时为混合流程。
流程图可以了解到OpenIDConnect的整个身份验证及授权步骤,而最终具体的实现就直接对应到授权码流程、隐式流程和混合流程。
OIDC授权码流程及实现
下面以授权码流程为例进行详细解说,首先授权码流程步骤如下:
![](https://img.qb5200.com/download-x/20210201/26625.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\590f3e8a4a194f38b82c5c18ca88bb94\clipboard.png)
授权码流程一共有八个步骤,简单来说就是由客户端向身份验证服务器发起身份验证请求(响应类型为code),身份验证服务器向用户进行身份验证及用户允许和授权操作后,将授权码发送给客户端,客户端通过授权码向身份验证服务器的Token中阶段获取ID和Access Token,然后对ID Token进行验证并获取用户的ID信息。
接下来使用之前创建的IdentityServer来实现基于授权码流程的身份验证与授权。
上一篇文章已经对IdentityServer添加了用户及Asp.net Core Identity组件的支持,目前无需再进行任何修改,换句话说现在的IdentityServer已经能够颁发ID Token了,完成授权码流程仅需要Client的支持,Client我们使用之前文章中添加的Web API来演示,把原有的基于Jwt Bearer的身份验证代码注释掉,添加基于cookie以及OIDC的身份验证服务(注:添加OIDC的时候Client信息需要与IdentityServer中数据库一致,并且相应的Client配置的重定向地址需要与该应用程序匹配):
![](https://img.qb5200.com/download-x/20210201/26627.png)
确保“interactive”这个客户端的重定向地址为“应用程序地址/signin-oidc”,这里需要注意的是这个重定向地址实际上是客户端(WebApi)通过方法.AddOpenIdConnect添加的用于处理odic身份验证的身份验证处理器:
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\9eab47b237214ab9a493cd1aabf60241\clipboard.png)
然后启动项目,并访问受保护的资源:https://localhost:51001/WeatherForecast
就会跳转到身份验证服务器的登录页面:
![](https://img.qb5200.com/download-x/20210201/26630.png)
下面是登录页面url信息:
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\8243e96b44cc446e8dedf30c85105a71\clipboard.png)
可以看到三个参数:
1、第一个参数是因为客户端向Identity服务器发起身份验证请求(Authentication Request),但由于终端用户还未登录,所以先跳转登录页面,当完成登陆后将返回/connect/authorize/callback。
2、第三个参数是响应类型,值为code,代表当前使用授权码流程。
3、第二个参数重定向uri是完成授权后,将携带授权码重定向的地址,也就是web Api程序的oidc身份验证地址。
对于授权码流程的八个步骤来说,它完成了前三个,目前处于终端用户身份验证(未完成-还未输入用户名密码)阶段:
![](https://img.qb5200.com/download-x/20210201/26633.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\4e53c27704bf4c43894b7f2705a5a9ba\clipboard.png)
输入用户名密码登录后,就可以看到受保护的内容了:
![](https://img.qb5200.com/download-x/20210201/26635.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\d875e850b717489a8c99ef53f7ef2bbd\clipboard.png)
这里完成授权码流程的后四个步骤:
![](https://img.qb5200.com/download-x/20210201/26637.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\aac5e33ee8044d6284f6283cbca19192\clipboard.png)
这四个步骤都是由客户端的oidc身份验证处理器完成的(具体实现参见:https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs)
整个过程主要是应用程序(Client)和身份验证服务器进行交互并完成,相关的授权码、ID token以及Access Token均“未”发送到浏览器中,所以授权码流程也是最为安全的流程。
如何在客户端获得相应的Access Token呢?通过HttpContext.GetTokenAsync("token name");即可获得相应的Token:
![](https://img.qb5200.com/download-x/20210201/26639.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\49cdf61025d141009a1bd8294bf5f3b4\clipboard.png)
ID Token:
![](https://img.qb5200.com/download-x/20210201/26641.png)
Access Token:
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\0a4bf2cd7713415ebad73d4cff72701f\clipboard.png)
可以通过HttpContext获取Token的原因是在添加oidc身份验证服务时,将SaveTokens选项设置为true,当身份验证成功后程序会自动将各类Token存储到AuthenticationProperties中,最终加密写入Cookie里面,所以虽然数据保存在浏览器,但由于加密缘故,所以之前才说他们均“未”发送到浏览器中:
![](https://img.qb5200.com/download-x/20210201/26644.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\de7c566391d1404a91fb0712eec9378f\clipboard.png)
那么授权码流程最后一步要如何实现呢?这里有两种方法,其一是通过获取access token之后直接在程序中访问身份验证服务器的UserInfo Endpoint获取,另外就是将oidc选项的GetClaimsFromUserInfoEndpoint设置为true即可:
![](https://img.qb5200.com/download-x/20210201/26646.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\0c2e6aa0afc94310a24a6d98b37543ba\clipboard.png)
未获取UserInfo的User Claims信息:
![](https://img.qb5200.com/download-x/20210201/26648.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\035ef953573e40fa9feac85cda1c3f20\clipboard.png)
获取UserInfo后的User Claims信息,可以看到多了一个name的claim(关于为什么只有一个claim后续文章中再进行说明):
![](https://img.qb5200.com/download-x/20210201/26650.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\0684394319334dfdb579faac9412bdc0\clipboard.png)
OIDC隐式流程及实现
既然最复杂的授权码流程已经能够实现了,那么简单的隐式流程肯定没问题,下面就演示一下如何通过隐式流程将token直接获取到浏览器中。
首先创建一个支持隐式流程的client:
![](https://img.qb5200.com/download-x/20210201/26652.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\a326afcd8fb74a5596d0b7d1c7ac6e4a\clipboard.png)
注:数据库中创建client复制已有的数据修改即可,client密码是加密存储的,复制后使用被复制的client密码即可,另外因为需要将token信息发送到浏览器,所以需要将client信息中的“AllowAccessTokenViaBrowser”设置为1。
将client的信息配置到client应用上:
![](https://img.qb5200.com/download-x/20210201/26654.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\84e7ccd728444dde8f23e839af78518f\clipboard.png)
隐式流程要求响应类型为id_token或id_token及token。
配置完成后运行程序并携带以下参数,直接访问IdentityServer的授权终结点:
https://localhost:5001/connect/authorize?response_type=id_token token&scope=openid scope2&client_id=test1&state=22222&redirect_uri=https://localhost:51001/swagger/index.html&nonce=11111
![](https://img.qb5200.com/download-x/20210201/26656.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\ecba0a5cc0ed43ac90e3d72071f142bd\clipboard.png)
登录成功后程序将自动跳转到参数redirect_uri指定的路径,并且携带token及相关信息:
![](https://img.qb5200.com/download-x/20210201/26658.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\0eb3279b363c46de8f5648cad503a160\clipboard.png)
将整个url格式化后获得以下结果:
![](https://img.qb5200.com/download-x/20210201/26660.png)
![](C:\Users\yqszt\AppData\Local\YNote\data\yqsztt@163.com\914197c1c2da407396f694114c95e2a5\clipboard.png)
小结
本篇文章介绍了OpenIDConnect的基本概念,并通过已有的IdentitySever程序演示了基于授权码和隐式流程,其中授权码模式是一种较为安全的模式,所有的关键的数据包括授权码以及各类token均在client的后台完成,如果需要可以把相关的token加密后以cookie的方式放到客户端以供后续使用。而隐式模式可以将各类token返回到浏览器中,这种模式可以在单页应用中使用,将token交由js来进行管理并用于受保护资源的访问。
另外到目前为止我们可以看到的是IdentityServer或者说IdentityServer4的作用就是校验Client信息、终端用户的用户名密码信息,然后生成授权码以及各类token,而token的验证和使用实际上还是在Client中进行的。
最后OIDC是一个身份验证协议,那么身份验证和授权在Asp.net core应用程序中是如何体现的呢?下篇文章就来聊聊这个问题。
参考:
https://openid.net/specs/openid-connect-core-1_0.html
https://openid.net/connect/faq/
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc
本文链接:https://www.cnblogs.com/selimsong/p/14355150.html
加载全部内容