首先考虑ASP.NET Core MVC 作为客户端应用的情况.
ASP.NET Core MVC是机密客户端(Confidential Client), 它是传统的服务器端Web应用.
它需要长时间访问(long-lived access), 所以需要refresh token. 那么它可以使用Authorization Code Flow或Hybrid Flow.
在这里Hybrid Flow是相对高级一些的, 它可以让客户端首先从授权端点获得一个ID Token并通过浏览器(front-channel)传递过来, 这样我们就可以验证这个ID Token. 如果验证成功然后, 客户端再打开一个后端通道(back-channel), 从Token端点获取Access Token.
下面是OpenID Connect官方文档给出的一个身份认证请求的例子.
第一行的URI: "/authorize" 就是授权端点(Authorization Endpoint), 它位于身份提供商(Identity provider, IDP)那里. 这个URI可以从前面介绍的discovery document里面找到.
第二行 response_type=code id_token, 它决定了采取了哪一种Hybrid流程(参考上面那三个图).
第三行 client_id=xxxx, 这是客户端的身份标识.
第四行 redirect_uri=https...., 这是客户端那里的重定向端点(Redirection Endpoint).
第五行 scope=openid profile email, 这就是客户端所请求的scopes.
再看一遍这张图:
为什么要返回两次ID Token呢? 这是因为第(4)步里面请求Token的时候要求客户端身份认证, 这时请求Token的时候需要提供Authorization Code, Client ID和 Client Secret, 这些secret并不暴露给外界, 这些东西是由客户端服务器通过后端通道传递给Token端点的. 而第一次获得的ID Token是从前端通道(浏览器)返回的.
当这个ID Token被验证通过之后, 也就证明了当前用户到底是谁.
下面简单对比一下前端和后端通道:
创建好后回到IdentityProvider项目, 添加一个Client:
这里ClientName是客户端名称, 它会出现在用户同意授权的页面. 流程选择的是Hybrid. 这里暂时只请求OpenId这一个Scope, 以便只返回ID Token, 在GetIdentityResources()方法里我知道支持这个scope. 这个流程的授权码和tokens是通过跳转来传递到浏览器的URI上面的, 所以我需要一个URI来接收这些东西, 而RedirectUris里面的URI就是允许做这个工作的URI.
下面继续配置MVC客户端 (官方文档: https://identityserver4.readthedocs.io/en/release/quickstarts/3_interactive_login.html#creating-an-mvc-client).
在MVC客户端的Startup的ConfigureServices里:
下面的文字都是翻译的官方文档.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 这句话是指, 我们关闭了JWT的Claim 类型映射, 以便允许well-known claims.
这样做, 就保证它不会修改任何从Authorization Server返回的Claims.
这里通过调用services.AddAuthentication()方法来添加和配置身份认证中间件.
这里我们使用Cookie作为验证用户的首选方式, 而DefaultScheme = "Cookies", 这个"Cookies"字符串是可以任意填写的, 只要与后边的一致即可. 但是如果同一个服务器上有很多应用的话, 这个Scheme的名字不能重复.
而把DefaultChanllangeScheme设为"oidc", 这个名字与后边配置OpenIdConnect的名字要一样. 当用户需要登陆的时候, 将使用的是OpenId Connect Scheme.
然后的AddCookie, 其参数是之前配置的DefaultScheme名称, 这配置了Cookie的处理者, 并让应用程序为我们的DefaultScheme启用了基于Cookie的身份认证. 一旦ID Token验证成功并且转化为Claims身份标识后, 这些信息就将会保存于被加密的Cookie里.
下面的AddOpenIdConnect()方法添加了对OpenID Connect流程的支持, 它让配置了用来执行OpenId Connect 协议的处理者.
这个处理者会负责创建身份认证请求, Token请求和其它请求, 并负责ID Token的验证工作.
它的身份认证scheme就是之前配置的"oidc", 它的意思就是如果该客户端的某部分要求身份认证的时候, OpenID Connect将会作为默认方案被触发(因为之前设置的DefaultChallengeScheme是"oidc", 和这里的名字一样).
SignInScheme和上面的DefaultScheme一致, 它保证身份认证成功的结果将会被保存在方案名为"Cookies"的Cookie里.
Authority就是Identity Provider的地址.
ClientId和Secret要与IdentityProvider里面的值一样.
ResponseType就是前面介绍过的.
请求的Scope有openid和profile, 其实中间件默认也包括了这些scope, 但是写出来更明确一些.
SaveTokens=true, 表示允许存储从Identity Provider那里获得的tokens.
然后配置管道:
确保中间件在UseMvc()之前调用.
还要确保监听地址和IdentityProvider里面配置的Client一致:
然后我对HomeController要求身份认证:
随后修改一下About方法, 我仅仅是想展示token的数据:
这个token来自于cookie.
再修改About的页面:
下面测试一下MVC客户端的身份认证:
同时运行Identity Provider 和 Mvc 两个程序, 最好使用控制台, 这样如果有错误的话就可以方便的看到相关信息了.
在访问Mvc的首页时, 会自动跳转到Identity Provider上:
具体的请求可以通过Chrome的Developer Tools看到:
在Identity Provider的控制台上, 也可以看到相关信息:
登录用户之后, 就会看到征求用户同意授权的页面:
点击Yes即可.
然后浏览器会调转会MVC Client, 通过Chrome的工具查看:
可以看到跳转回来的时候是到了signin-oidc这个地址, 它就是我之前在Identity Provider里面Client的RedirectUri.
与此同时, 可以在Identity Provider的控制台看到, MVC客户端通过后端通道向Token端点发出了Token请求, 这个过程用户是不会发现的:
这个过程就和前面图示的一样, 最后从token端点请求到新的ID Token之后, 会再次进行验证, 然后会通过它创建Claims Identity, 也就是前面代码里的User.Claims.
这个身份验证的凭据都会保存在加密的Cookie里面:
来到About菜单:
最上面可以看到ID Token的值.
sid是sessionid.
sub是用户的subjectid
idp是本地的.
我们可以在jwt.io来解析一下这个ID Token
解码之后的ID Token:
这里的内容以后再讲.
登录好用之后, 就考虑一下登出.
再_Layout.cshtml里面添加登出按钮, 这部分官方文档都有:
然后建立Action方法:
首先要清除本地的Cookie, 这个Cookie的名字要与之前配置的默认方案里的名字一致, 这一步就相当于登出MVC客户端.
后一行代码的作用是跳转回到Identity Provider, 然后用户可以继续登出IDP, 也就是IDP会清除它的Cookie.
但是登出之后, 用户会留在Identity Provider那里:
查看IDP的控制台, 可以看到这个失败: Invalida post logout URI:
这是因为我们配置Client的时候没有指定在登出之后的跳转URI地址.
回到IDP的客户端配置那里:
添加PostLogoutRedirectUris属性, 里面这个值是就是默认的登出后跳转地址.
再次操作后, 效果如下:
点击here之后会回到MVC客户端, 然后由于权限问题会又立即跳转到IDP.
如果想让这个过程自动跳转, 可以修改IDP的Quickstart/Account/AccountOptions类里面的这个值改成true:
再次操作, 跳转就是自动完成的了.
查看解码的ID Token, 可以看到里面包含了这些claims:
这里除了sub之外, 并没有关于用户的其他信息.
我们可以通过指定参数来要求在ID Token里面返回用户其他的claims, 但是由于id token是从URI进行传输的, 而浏览器会有URI的长度限制, 所以尽量让token小点, 以免超限.
为了获得用户其他的claims, 客户端应用可以使用用户信息端点, 这需要用access token和相关claims对应的scopes.
首先在MVC客户端配置, GetClaimsFromUserInfoEndpoit为true, 并请求profile scope:
随后在IDP那里为MVC Client添加上profile scope:
再次执行操作, 回到About页面:
可以看到profile scope里对应的这两个claims值已经出来了.
再把ID Token到jwt.io去解码一下:
可以看到这两个claims并不在ID Token里面, 这就说明它们来自用户信息端点.
在ID Token里面的东西(官方文档有介绍: http://openid.net/specs/openid-connect-core-1_0.html#IDToken):
sub是用户的subjectid, 也就是用户的身份标识.
iss是ID Token的发行者.
aud是这个token的目标观众, 这里就是MVC客户端的clientid.
nbf是指在这个时间之前, ID Token是不被接受的.
exp是ID Token的过期时间.
iat是这个JWT token发行的时间.
auth_time是原始身份认证的时间.
amr是指身份认证的方法. 这里用的是pwd, 密码.
nonce, 它是Number only to be used once的意思. 它是一个字符串, 使用ID Token和客户端Session关联, 来减少重复攻击.
最后是at_hash, 其实还有c_hash, 它们分别代表Access Token Hash和Code Hash. 就是通过某种方式对Access Token和Code的Base64编码. 它们可以用来把Access Token或Authorization Code链接到这个ID Token上.
如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!
加入交流群
请使用微信扫一扫!