使用Identity Server 4建立Authorization Server (4)

预备知识: 学习Identity Server 4的预备知识

第一部分: 使用Identity Server 4建立Authorization Server (1)

第二部分: 使用Identity Server 4建立Authorization Server (2)

第三部分: 使用Identity Server 4建立Authorization Server (3)

上一篇讲了使用OpenId Connect进行Authentication.

下面讲

Hybrid Flow和Offline Access

目前我们解决方案里面有三个项目 Authorization Server, Web api和Mvc Client. 在现实世界中, 他们可能都在不同的地方.

现在让我们从MvcClient使用从Authorization Server获取的token来访问web api. 并且确保这个token不过期.

现在我们的mvcClient使用的是implicit flow, 也就是说, token 被发送到client. 这种情况下 token的生命可能很短, 但是我们可以重定向到authorization server 重新获取新的token.

例如, 在SPA(Single Page Application)中, implicit flow基本上就是除了resource owner password flow 以外唯一合适的flow, 但是我们的网站可能会在client没使用网站的时候访问api, 为了这样做, 不但要保证token不过期, 我们还需要使用别的flow. 我们要介绍一下authorization code flow. 它和implicit flow 很像, 不同的是, 在重定向回到网站的时候获取的不是access token, 而是从authorization server获取了一个code, 使用它网站可以交换一个secret, 使用这个secret可以获取access token和refresh tokens.

Hybrid Flow, 是两种的混合, 首先identity token通过浏览器传过来了, 然后客户端可以在进行任何工作之前对其验证, 如果验证成功, 客户端就会再打开一个通道向Authorization Server请求获取access token.

首先在Authorization server的InMemoryConfiguration添加一个Client:

new Client{ClientId = "mvc_code",ClientName = "MVC Code Client",AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,                    ClientSecrets ={new Secret("secret".Sha256())},RedirectUris = { "http://localhost:5002/signin-oidc" },PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },AllowedScopes = new List<string>{IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.Email,                        "socialnetwork"},                    AllowOfflineAccess = true,AllowAccessTokensViaBrowser = true}

首先肯定要修改一下ClientId.

GrantType要改成Hybrid或者HybrdAndClientCredentials, 如果只使用Code Flow的话不行, 因为我们的网站使用Authorization Server来进行Authentication, 我们想获取Access token以便被授权来访问api. 所以这里用HybridFlow.

还需要添加一个新的Email scope, 因为我想改变api来允许我基于email来创建用户的数据, 因为authorization server 和 web api是分开的, 所以用户的数据库也是分开的. Api使用用户名(email)来查询数据库中的数据.

AllowOfflineAccess. 我们还需要获取Refresh Token, 这就要求我们的网站必须可以"离线"工作, 这里离线是指用户和网站之间断开了, 并不是指网站离线了.

这就是说网站可以使用token来和api进行交互, 而不需要用户登陆到网站上. 

修改MvcClient的Startup的ConfigureServices:

public void ConfigureServices(IServiceCollection services){services.AddMvc();JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();services.AddAuthentication(options =>{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{options.SignInScheme = "Cookies";options.Authority = "http://localhost:5000";options.RequireHttpsMetadata = false;options.ClientId = "mvc_code";                options.ClientSecret = "secret";options.ResponseType = "id_token code";                options.Scope.Add("socialnetwork");options.Scope.Add("offline_access");options.SaveTokens = true;                options.GetClaimsFromUserInfoEndpoint = true;});}


首先改ClientId和Authorization server一致. 这样用户访问的时候和implicit差不多, 只不过重定向回来的时候, 获取了一个code, 使用这个code可以换取secret然后获取access token.

所以需要在网站(MvcClient)上指定Client Secret. 这个不要泄露出去.

还需要改变reponse type, 不需要再获取access token了, 而是code, 这意味着使用的是Authorization Code flow.

还需要指定请求访问的scopes: 包括 socialnetwork api和离线访问

最后还可以告诉它从UserInfo节点获取用户的Claims.

运行

点击About, 重定向到Authorization Server:

同时在Authorization Server的控制台可以看见如下信息:

这里可以看到请求访问的scope, response_type. 还告诉我们respose mode是from_post, 这就是说, 在这登陆后重定向回到网站是使用的form post方式.

然后登陆:

这里可以看到请求访问的范围, 包括个人信息和Application Access.

点击Yes, Allow:

重定向回到了网站. 这里看起来好像和以前一样. 但是如果看一下Authorization Server的控制台:

就会看到一个request. 中间件发起了一个请求使用Authorization Code和ClientId和secret来换取了Access token.

当Authorization验证上述信息后, 它就会创建一个token.

打印Refresh Token

修改MvcClient的About.cshtml:

@using Microsoft.AspNetCore.Authentication<div><strong>id_token</strong><span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span></div><div><strong>access_token</strong><span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span></div><div><strong>refresh_token</strong><span>@await ViewContext.HttpContext.GetTokenAsync("refresh_token")</span>
</div><dl>@foreach (var claim in User.Claims){        <dt>@claim.Type</dt><dd>@claim.Value</dd>}</dl>

刷新页面:

看到了refresh token.

这些token包含了什么时候过期的信息.

如果access token过期了, 就无法访问api了. 所以需要确保access token不过期. 这就需要使用refresh token了.

复制一下refresh token, 然后使用postman:

使用这个refresh token可以获取到新的access token和refresh_token, 当这个access_token过期的时候, 可以使用refresh_token再获取一个access_token和refresh_token......

而如果使用同一个refresh token两次, 就会得到下面的结果:

看看Authorization Server的控制台, 显示是一个invalid refresh token:

所以说, refresh token是一次性的.

获取自定义Claims

web api 要求request请求提供access token, 以证明请求的用户是已经授权的. 现在我们准备从Access token里面提取一些自定义的Claims, 例如Email.

看看Authorization Server的Client配置:

Client的AllowedScopes已经包括了Email. 但是还没有配置Authorization Server允许这个Scope. 所以需要修改GetIdentityResources()(我自己的代码可能改名成IdentityResources()了):

public static IEnumerable<IdentityResource> IdentityResources(){            return new List<IdentityResource>{                new IdentityResources.OpenId(),                new IdentityResources.Profile(),                new IdentityResources.Email()};}

然后需要为TestUser添加一个自定义的Claims;

public static IEnumerable<TestUser> Users(){            return new[]{                new TestUser{SubjectId = "1",Username = "mail@qq.com",Password = "password",                    Claims = new [] { new Claim("email", "mail@qq.com") }}};}

然后需要对MvcClient进行设置, Startup的ConfigureServices:

public void ConfigureServices(IServiceCollection services){services.AddMvc();JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();services.AddAuthentication(options =>{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{options.SignInScheme = "Cookies";options.Authority = "http://localhost:5000";options.RequireHttpsMetadata = false;options.ClientId = "mvc_code";options.ClientSecret = "secret";options.ResponseType = "id_token code";options.Scope.Add("socialnetwork");options.Scope.Add("offline_access");                options.Scope.Add("email");options.SaveTokens = true;options.GetClaimsFromUserInfoEndpoint = true;});}

添加email scope. 所以MvcClient就会也请求这个scope.

运行:

这时在同意(consent)页面就会出现email address一栏.

同意之后, 可以看到email已经获取到了.

使用Access Token调用Web Api

首先在web api项目建立一个IdentityController:

namespace WebApi.Controllers
{[Route("api/[controller]")] 
   
public class IdentityController: Controller{[Authorize][HttpGet]      
       
public IActionResult Get(){            var username = User.Claims.First(x => x.Type == "email").Value;            return Ok(username);            //return new JsonResult(from c in User.Claims select new { c.Type, c.Value});        }} }


我们想要通过自定义的claim: email的值.

然后回到mvcClient的HomeController, 添加一个方法:

        [Authorize]       
       
public async Task<IActionResult> GetIdentity(){            
var token = await HttpContext.GetTokenAsync("access_token");            using (var client = new HttpClient()){client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);                var content = await client.GetStringAsync("http://localhost:5001/api/identity");// var json = JArray.Parse(content).ToString();        
        
return Ok(new { value = content });}}

这里首先通过HttpContext获得access token, 然后在请求的Authorization Header加上Bearer Token.

让我们运行一下, 并在MvcClient和Web Api里面都设好断点,

登录后在浏览器输入 http://localhost:5002/Home/GetIdentity 以执行GetIdenttiy方法, 然后进入Web Api看看断点调试情况:

由于我们已经授权了, 所以可以看到User的一些claims, 而其中没有email这个claim. 再运行就报错了.

这是怎么回事? 我们回到About页面, 复制一下access_token, 去jwt.io分析一下:

确实没有email的值, 所以提取不出来.

所以我们需要把email添加到access token的数据里面, 这就需要告诉Authorization Server的Api Resource里面要包括User的Scope, 因为这是Identity Scope, 我们想要把它添加到access token里:

修改Authorization Server的InMemoryConfiguration的ApiResources():

public static IEnumerable<ApiResource> ApiResources(){           
 
return new[]{              
 
new ApiResource("socialnetwork", "社交网络")                {UserClaims = new [] { "email" }}};}


这对这个Api Resouce设置它的属性UserClaims, 里面写上email.

然后再运行一下程序, 这里需要重新登陆, 首先分析一下token:

有email了. 

然后执行GetIdentity(), 在web api断点调试, 可以看到UserClaims已经包含了email:

上面这些如果您不会的话, 需要整理总结一下.

用户使用Authorization Server去登录网站(MvcClient), 也就是说用户从网站跳转到第三方的系统完成了身份的验证, 然后被授权可以访问web api了(这里讲的是用户通过mvcClient访问api). 当访问web api的时候, 首先和authorization server沟通确认access token的正确性, 然后就可以成功的访问api了.

刷新Access Token

根据配置不同, token的有效期可能差别很大, 如果token过期了, 那么发送请求之后就会返回401 UnAuthorized.

当然如果token过期了, 你可以让用户重定向到Authorization Server重新登陆,再回来操作, 不过这样太不友好, 太繁琐了.

既然我们有refresh token了, 那不如向authorization server请求一个新的access token和refresh token. 然后再把这些更新到cookie里面. 所以下次再调用api的时候使用的是新的token.

在MvcClient的HomeController添加RefreshTokens()方法:

首先需要安装IdentityModel, 它是OpenIdConnect, OAuth2.0的客户端库:

        [Authorize]       
        
public async Task RefreshTokensAsync(){            

var authorizationServerInfo = await DiscoveryClient.GetAsync("http://localhost:5000/");            var client = new TokenClient(authorizationServerInfo.TokenEndpoint, "mvc_code", "secret");        
   
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");            var response = await client.RequestRefreshTokenAsync(refreshToken);            var identityToken = await HttpContext.GetTokenAsync("identity_token");            var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);  
         
var tokens = new[]{            
   
new AuthenticationToken{Name = OpenIdConnectParameterNames.IdToken,Value = identityToken},            
   
new AuthenticationToken{Name = OpenIdConnectParameterNames.AccessToken,Value = response.AccessToken},          
      
new AuthenticationToken{Name = OpenIdConnectParameterNames.RefreshToken,Value = response.RefreshToken},            
   
new AuthenticationToken{Name = "expires_at",Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)}};          
 
var authenticationInfo = await HttpContext.AuthenticateAsync("Cookies");authenticationInfo.Properties.StoreTokens(tokens);            await HttpContext.SignInAsync("Cookies", authenticationInfo.Principal, authenticationInfo.Properties);}

首先使用一个叫做discovery client的东西来获取Authorization Server的信息. Authorization Server里面有一个discovery节点(endpoint), 可以通过这个地址查看: /.well-known/openid-configuration. 从这里可以获得很多信息, 例如: authorization节点, token节点, 发布者, key, scopes等等.

然后使用TokenClient, 参数有token节点, clientId和secret. 然后可以使用这个client和refreshtoken来请求新的access token等. 

找到refresh token后, 使用client获取新的tokens, 返回结果是tokenresponse. 你可以设断点查看一下token reponse里面都有什么东西, 这里就不弄了, 里面包括identitytoken, accesstoken, refreshtoken等等.

然后需要找到原来的identity token, 因为它相当于是cookie中存储的主键...

然后设置一下过期时间.

然后将老的identity token和新获取到的其它tokens以及过期时间, 组成一个集合.

然后使用这些tokens来重新登陆用户. 不过首先要获取当前用户的authentication信息, 使用HttpContext.AuthenticateAsync("Cookies"), 参数是AuthenticationScheme. 然后修改属性, 存储新的tokens.

最后就是重登录, 把当前用户信息的Principal和Properties传进去. 这就会更新客户端的Cookies, 用户也就保持登陆并且刷新了tokens.

先简单调用一下这个方法:


[Authorize]       
 
public async Task<IActionResult> GetIdentity(){        
   
await RefreshTokensAsync();  
         
var token = await HttpContext.GetTokenAsync("access_token");            using (var client = new HttpClient()){client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);                var content = await client.GetStringAsync("http://localhost:5001/api/identity");                //var json = JArray.Parse(content).ToString();return Ok(new { value = content });}}

正式生产环境中可不要这么做, 正式环境中应该在401之后, 调用这个方法, 如果再失败, 再返回错误.

运行一下:

发现获取的access token是空的, 一定是哪出现了问题, 看一下 authorization server的控制台:

说refresh token不正确(应该是内存数据和cookie数据不匹配). 那就重新登陆.

看断点, 有token了:

并且和About页面显示的不一样, 说明刷新token了.

也可以看一下authorization server的控制台:

说明成功请求了token.

原文地址:http://www.cnblogs.com/cgzl/p/7795121.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux操作命令

uname -r 显示正在使用的内核版本、 docker exec -it mytomcat bash 进入tomcat界面 pwd 显示工作路径 ls 查看当前目录中的文件 docker docker run -d -p 8080:8080 tomcat 启动tomcat镜像 docker start id 编辑文件 touch 文件名 创建文件 mkdir dir1 创建一个叫做 ‘dir1…

Hadoop入门(十八)Mapreduce的倒排索引程序

一、简介 "倒排索引"是文档检索系统中最常用的数据结构&#xff0c;被广泛地应用于全文搜索引擎。它主要是用来存储某个单词&#xff08;或词组&#xff09;在一个文档或一组文档中的存储位置的映射&#xff0c;即提供了一种根据内容来查找文档的方式。由于不是根据…

.NET Core跨平台的奥秘[中篇]:复用之殇

在《.NET Core跨平台的奥秘[上篇]&#xff1a;历史的枷锁》中我们谈到&#xff1a;由于.NET是建立在CLI这一标准的规范之上&#xff0c;所以它天生就具有了“跨平台”的基因。在微软发布了第一个针对桌面和服务器平台的.NET Framework之后&#xff0c;它开始 “乐此不疲” 地对…

jozj4010-我才不是萝莉控呢【哈夫曼树】

正题 题目大意 从(n,1)(n,1)(n,1)到(1,1)(1,1)(1,1)&#xff0c;一个数组AAA&#xff0c;满足Ai≥Ai1A_i\geq A_i1Ai​≥Ai​1 每次有两个选择走到(x−1,y1)(x-1,y1)(x−1,y1)&#xff0c;或(x,⌊y/2⌋)(x,\lfloor y/2\rfloor)(x,⌊y/2⌋)。后者需要消耗∑ixnAi\sum_{ix}^nA_i…

java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized

在application.yml中 spring:datasource:username: rootpassword:url: jdbc:mysql://127.0.0.1:3306/testdriver-class-name: com.mysql.cj.jdbc.Driver结果报错 java.sql.SQLException: The server time zone value ‘&#xfffd;й&#xfffd;&#xfffd;&#xfffd;׼ʱ…

Hadoop入门(十一)Mapreduce的InputFomrat各种子类

一、TextInputFormat extends FileInputFomrat<LongWritable,Text> 是默认读取文件的切分器&#xff0c;其内的LineRecordReader:用来读取每一行的内容&#xff0c; LineRecordReader:内的 nextKeyValue(){}中&#xff0c;key的赋值在&#xff1a; initialize()方法内&…

欢乐纪中某B组赛【2019.1.21】

前言 成功翻车 成绩 RankRankRank是有算别人的 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC1414142017hzb2017hzb2017hzb8080803030300005050501414142017wyc2017wyc2017wyc8080800003030305050501414142017xxy2017xxy2017xxy8080803030300005050504444442017lw2…

极简版ASP.NET Core学习路径

拒绝承认这是一个七天速成教程&#xff0c;即使有这个效果&#xff0c;我也不愿意接受这个名字。嗯。 这个路径分为两块&#xff1a; 实践入门 理论延伸 有了ASP.NET以及C#的知识以及项目经验&#xff0c;我们几乎可以不再需要了解任何新的知识就开始操练&#xff0c;实践才是…

spring boot连接数据库

applicat.yml spring:datasource:username: rootpassword:url: jdbc:mysql://localhost:3306/test?useUnicodetrue&useJDBCCompliantTimezoneShifttrue&useLegacyDatetimeCodefalse&serverTimezoneUTCdriver-class-name: com.mysql.cj.jdbc.Drivertest文件夹下测…

依存句法分析的任务以及形式化定义

转载自 依存句法分析的任务以及形式化定义 依存句法分析的任务以及形式化定义 1、依存句法分析的形式化定义 在依存句法中&#xff0c;共同的基本假设是&#xff1a;句法结构本质上包含词和词对之间的关系。这种关系就是依存关系&#xff08;dependency relations&#xff…

jzoj3084-超级变变变【数学】

正题 题目大意 定义函数 f(x){x−1(x%21)x/2(x%20)f(x)\left\{\begin{matrix} &amp;x-1(x\%21)\\ &amp; x/2(x\%20) \end{matrix}\right.f(x){​x−1(x%21)x/2(x%20)​ 一次变化是将xf(x)xf(x)xf(x) 求A∼BA\sim BA∼B之间有多少个数可以变化到kkk 解题思路 其实就是…

使用Identity Server 4建立Authorization Server (5)

预备知识: 学习Identity Server 4的预备知识 第一部分: 使用Identity Server 4建立Authorization Server (1) 第二部分: 使用Identity Server 4建立Authorization Server (2) 第三部分: 使用Identity Server 4建立Authorization Server (3) 第四部分: 使用Identity Server 4建立…

idea如何安装lombok

https://github.com/mplushnikov/lombok-intellij-plugin/releases &#xff0c;Plugins -> Install plugin from disk… 选择下载的zip包安装&#xff0c;重启idea即可。 依赖包 <dependency><groupId>org.projectlombok</groupId><artifactId>lom…

好好说说Java中的常量池之Class常量池

转载自 好好说说Java中的常量池之Class常量池 在Java中&#xff0c;常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中&#xff0c;一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解&#xff0c;几道简单的String面试…

jzoj3085-图的计数【组合数,数论】

正题 题目大意 求有多少个m条边的有向图使得1到n的最短路长度为n-1 解题思路 首先长度为n−1n-1n−1那么就是1到n得先是一条链。在链上加m−n1m-n1m−n1条边且不能加如捷径边。 捷径边的条数为Cn−12C_{n-1}^2Cn−12​&#xff0c;然后可以加的边数就是n∗n−Cn−12n*n-C_{n-…

spring cloud+.net core搭建微服务架构:Api授权认证(六)

前言 这篇文章拖太久了&#xff0c;因为最近实在太忙了&#xff0c;加上这篇文章也非常长&#xff0c;所以花了不少时间&#xff0c;给大家说句抱歉。好&#xff0c;进入正题。目前的项目基本都是前后端分离了&#xff0c;前端分Web&#xff0c;Ios,Android。。。,后端也基本是…

如何用spring boot写一个注册页面

环境准备&#xff1a; java集成开发环境&#xff1a;IDEA 数据库&#xff1a;Mysql Maven 最好在安装有个navicat&#xff08;数据库可视化界面&#xff09; 安装好上述几个软件后 总结下&#xff1a;五步 1、创建新的工程 2、创建建applicatiom.yml 3、创建entity层 4、创建r…

Oracle入门(一)之入门级知识详解

转载自 Oracle入门级知识详解 一. Oracle基本介绍 1. 什么时候用Oracle数据库&#xff1f; SQL SERVER 号称百万级数据&#xff08;一个表的数据&#xff09;&#xff0c;但是其实做多20万条数据 超过20万条数据就用Oracle 2. Oracle的版本 Oracle8i/9i(internet)基于网络…

jzoj3086,luogu3831-[SHOI2012]回家的路【最短路,拆点】

正题 luogu评测记录:https://www.luogu.org/recordnew/lists?uid52918&pidP3831 题目大意 有n∗nn*nn∗n的铁路网走一格代价为2&#xff0c;mmm个中转站可以改变方向代价为1。求两个点之间的最短路。 解题思路 我们发现n∗nn*nn∗n很大&#xff0c;所以我们考虑根据mmm…

活动:北京Xamarin分享会第8期(2017年11月11日)

本期活动内容预告&#xff1a; 分享主题1: Tech Summit 2017大会课程 - 21世纪不动产使用Xamarin和Azure案例。 分享者&#xff1a;周岳, 微软MVP (Xamarin) , 北京视高盛景软件首席架构师 分享主题2: Tech Summit 2017大会课程 - AI: 清清爽爽几步&#xff0c;打造专属视觉分…