IdentityServer4:授权码模式
IdentityServer4:授权码模式
授权码模式比较适用于既有前端又有后端代码的项目,比如:想 AspNetCore MVC 客户端。
授权码模式的流程是:
用户访问客户端,客户端调转到认证服务器登录页面,让用户输入用户名和密码,并点击授权后,到一个 code(授权码),并放在回调URL中,
客户端再从这个回调的URL中解析到得到code(授权码),再通过code(授权码)向服务器发送请求获取 access_token。
授权码模式比简化(隐藏)模式多了一步:获取 code(授权码)。
Api 资源项目
创建项目
打开 VS,创建一个“AspNet Core WebApi” 项目, 名为:Dotnet.WebApi.Ids4.CustomerApi
依赖包
添加依赖包
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.3" />
添加认证方案
修改 Program.cs 为如下代码:
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace Dotnet.WebApi.Ids4.CustomerApi
{
public class Program
{
public static void Main(string[] args)
{
Console.Title = "CustomerAPI服务器";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//IdentityServer4地址
options.Authority = "https://localhost:6001";
//认证的ApiResource名称
options.Audience = "CustomerAPIResource";
//使用JWT认证类型
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
//配置跨域。
builder.Services.AddCors(options =>
{
options.AddPolicy("AppCors", policy => policy.WithOrigins("https://localhost:6021")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
);
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.Urls.Add("https://*:6011");
app.UseHttpsRedirection();
//启用跨域中间件
app.UseCors("AppCors");
//身份验证
app.UseAuthentication();
//授权
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
其中,
(1)添加 JWT 认证:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//IdentityServer4地址
options.Authority = "https://localhost:6001";
//认证的ApiResource名称
options.Audience = "CustomerAPIResource";
//使用JWT认证类型
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
其中, 设置认证服务器:options.Authority = "https://localhost:6001";
添加 Api
新增文件:Controllers/CustomerController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Dotnet.WebApi.Ids4.CustomerApi.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
/// <summary>
/// 获取客户信息列表。
/// </summary>
/// <returns></returns>
[HttpGet("GetList")]
public IEnumerable<Customer> GetList()
{
return new List<Customer>
{
new Customer{ Id=1, Name="客户1", Phone="电话1"},
new Customer{ Id=2, Name="客户2", Phone="电话2"},
new Customer{ Id=3, Name="客户3", Phone="电话3"},
};
}
}
}
其中:
(1)在控制器上添加特性:[Authorize],这样只有登录用户才能访问,这样就起到保护了Api资源的目的。
Customer.cs
namespace Dotnet.WebApi.Ids4.CustomerApi
{
/// <summary>
/// 客户实体模型
/// </summary>
public class Customer
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Phone { get; set; }
}
}
修改 Index 视图
Views/Home/Index.cshtml
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div style="margin:20px;">
<a href="/home/userinfo">用户信息</a>
<a href="/home/apidata">调用API</a>
</div>
添加 ApiData 视图
Views/Home/ApiData.cshtml
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div style="margin:20px;">
<a href="/home/userinfo">用户信息</a>
<a href="/home/apidata">调用API</a>
</div>
添加 UserInfo 视图
Views/Home/UserInfo.cshtml
@using Microsoft.AspNetCore.Authentication
@{
ViewData["Title"] = "用户信息";
}
<h1>@ViewData["Title"]</h1>
<h2>身份声明</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var c in User.Claims)
{
<tr>
<td>@c.Type</td>
<td>@c.Value</td>
</tr>
}
</table>
<h2>属性</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
{
<tr>
<td>@p.Key</td>
<td>@p.Value</td>
</tr>
}
</table>
认证服务器
创建项目
打开 VS,创建一个“AspNet Core 空” 项目,名为:Dotnet.WebApi.Ids4.AuthService
依赖包
添加依赖包
<PackageReference Include="IdentityServer4" Version="4.1.2" />
配置 IdentityServer4
创建文件:IdentityConfig.cs,添加如下代码:
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System.Security.Claims;
namespace Dotnet.WebApi.Ids4.AuthService
{
public static class IdentityConfig
{
/// <summary>
/// 配置IdentityResource。
/// </summary>
/// <returns></returns>
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource> {
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
/// <summary>
/// 配置API作用域。
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
//客户相关API作用域
new ApiScope("Customer.Read","读取客户信息。"),
new ApiScope("Customer.Add","添加客户信息。"),
//共享API作用域
new ApiScope("News","新闻信息。")
};
}
/// <summary>
/// 配置ApiResource。
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
//将多个具体的APIScope归为一个ApiResource。
return new List<ApiResource>()
{
new ApiResource("CustomerAPIResource", "客户资源")
{
Scopes={ "Customer.Read", "Customer.Add", "News" }
}
};
}
/// <summary>
/// 配置客户端应用。
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
#region 授权码模式
return new List<Client>
{
new Client
{
//客户端ID。
ClientId="WebMvcCodeClient",
//客户端名称。
ClientName="WebMvc-CodeClient",
//客户端密钥。
ClientSecrets =
{
new Secret("WebMvcCodeClient00000001".Sha256())
},
//Code表示授权码认证模式。
AllowedGrantTypes=GrantTypes.Code,
//是否支持授权操作页面,true表示显示授权界面,否则不显示。
RequireConsent=true,
//认证成功之后重定向的客户端地址,默认就是signin-oidc。
RedirectUris={ "https://localhost:6022/signin-oidc"},
//登出时重定向的地址,默认是signout-oidc。
PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
//是否允许返回刷新Token。
AllowOfflineAccess=true,
//指定客户端获取的AccessToken能访问到的API作用域。
AllowedScopes={
"Customer.Read",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
};
#endregion
}
/// <summary>
/// 配置用户。
/// </summary>
/// <returns></returns>
public static List<TestUser> GetUsers()
{
#region 授权码模式
return new List<TestUser>
{
new TestUser
{
SubjectId="00003",
Username="zhangsan",
Password="123456",
//添加声明信息
Claims =
{
new Claim(JwtClaimTypes.Name, "zhangsan"),
new Claim(JwtClaimTypes.GivenName, "san"),
new Claim(JwtClaimTypes.FamilyName, "zhang"),
new Claim(JwtClaimTypes.Email, "zhangsan@donet.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
}
}
};
#endregion
}
}
}
代码解析:
(1)授权码模式通过在客户端和认证服务器往返的URL来传递数据,使用了 Openid Connect 协议和 OAuth2.0 协议,故得在IdentityServer中配置 Openid 信息, 这是授权码模式必须得添加的:
/// <summary>
/// 配置IdentityResource。
/// </summary>
/// <returns></returns>
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource> {
new IdentityResources.OpenId(),
};
}
如果需要客户端要求能获取到用户信息,还得添加new IdentityResources.Profile()
, 如下所示:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource> {
new IdentityResources.OpenId(),
+ new IdentityResources.Profile()
};
}
(2)如下代码添加了 Client,并将其授权模式设置为:授权码模式:GrantTypes.Code, 并设置密码,和 Scope:
new Client
{
//客户端ID。
ClientId="WebMvcCodeClient",
//客户端名称。
ClientName="WebMvc-CodeClient",
//客户端密钥。
ClientSecrets =
{
new Secret("WebMvcCodeClient00000001".Sha256())
},
//Code表示授权码认证模式。
AllowedGrantTypes=GrantTypes.Code,
//是否支持授权操作页面,true表示显示授权界面,否则不显示。
RequireConsent=true,
//认证成功之后重定向的客户端地址,默认就是signin-oidc。
RedirectUris={ "https://localhost:6022/signin-oidc"},
//登出时重定向的地址,默认是signout-oidc。
PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
//是否允许返回刷新Token。
AllowOfflineAccess=true,
//指定客户端获取的AccessToken能访问到的API作用域。
AllowedScopes={
"Customer.Read",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
其中:
(1)设置授权模式: AllowedGrantTypes=GrantTypes.Code
(2)身份认证成功之后重定向到客户端的回调地址: RedirectUris={ "https://localhost:6022/signin-oidc"},
(3)退出时重定向到客户端的地址:PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
(4)跨域支持: AllowedCorsOrigins={"https://localhost:6021"},
(5)设置Scope:AllowedScopes = { ... }
(3) 添加用户:因为授权码模式需要用户参与,故得添加用户;
return new List<TestUser>
{
new TestUser
{
SubjectId="00001",
Username="Kevin",
Password="123456",
//添加声明信息
Claims =
{
new Claim(JwtClaimTypes.Name, "Kevin"),
new Claim(JwtClaimTypes.GivenName, "Mi"),
new Claim(JwtClaimTypes.FamilyName, "Kala"),
new Claim(JwtClaimTypes.Email, "Kevin@donet.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
}
}
};
集成 IdentityServer4
添加 IdentityServer4的Quickstart UI
因为授权码模式流程是:
用户访问客户端,客户端调转到认证服务器登录页面,让用户输入用户名和密码,并点击授权后,得到一个code(授权码),并放在回调URL中,
客户端再从这个回调的URL中解析到得到code(授权码),再通过code(授权码)向服务器发送请求获取 access_token。
从以上过程可以看到,IdentityServer4 认证服务器得有一个界面,好在已经一个开源项目:Quickstart UI,可以直接用即可。
下载 Quickstart UI:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI,
然后把 Quickstart、Views、wwwroot 三个文件夹复制到 Dotnet.WebApi.Ids4.AuthService 项目根目录下。
由于 Quickstart UI 使用了 AspNet Core 的 MVC 框架,所以得在 Program.cs 开启 MVC 框架:
//注册MVC服务。
builder.Services.AddControllersWithViews();
......
//终结点
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Program.cs
修改 Program.cs 为如下代码:
namespace Dotnet.WebApi.Ids4.AuthService
{
public class Program
{
public static void Main(string[] args)
{
Console.Title = "认证和授权服务器";
var builder = WebApplication.CreateBuilder(args);
//注册MVC服务。
builder.Services.AddControllersWithViews();
//注册IdentityServer4组件
builder.Services.AddIdentityServer()
.AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
.AddInMemoryApiScopes(IdentityConfig.GetApiScopes())
.AddInMemoryApiResources(IdentityConfig.GetApiResources())
.AddInMemoryClients(IdentityConfig.GetClients())
.AddTestUsers(IdentityConfig.GetUsers())
.AddDeveloperSigningCredential(); // 添加临时内存中的证书
var app = builder.Build();
//修改端口号
app.Urls.Add("https://*:6001");
//启用静态文件
app.UseStaticFiles();
//启用HTTPS转向
app.UseHttpsRedirection();
//启用路由
app.UseRouting();
//添加IDS4中间件。
//在浏览器中输入如下地址访问 IdentityServer4 的发现文档:https://localhost:6001/.well-known/openid-configuration
app.UseIdentityServer();
//授权
app.UseAuthorization();
//终结点
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
其中,app.Urls.Add("https://*:6001");
设置认证服务器的监听端口为:6001
授权码模式客户端
创建项目
创建一个 “AspNet Core MVC”,名为:Dotnet.WebApi.CodeClient
依赖包
添加依赖包:
<PackageReference Include="IdentityModel" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.3" />
Program.cs
将 Program.cs 的代码修改为;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.IdentityModel.Tokens.Jwt;
namespace Dotnet.WebApi.CodeClient
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
//去除映射,保留Jwt原有的Claim名称
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services.AddAuthentication(options =>{
//使用Cookies
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//使用OpenID Connect
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
//客户端ID
options.ClientId = "WebMvcCodeClient";
//客户端密钥
options.ClientSecret = "WebMvcCodeClient00000001";
//IdentityServer4认证服务器地址
options.Authority = "https://localhost:6001";
//响应授权码
options.ResponseType = "code";
//允许Token保存的Cookies中
options.SaveTokens = true;
//权限范围
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//设置允许获取刷新Token
options.Scope.Add("offline_access");
//设置访问的API范围
options.Scope.Add("Customer.Read");
//获取用户的Claims信息
options.GetClaimsFromUserInfoEndpoint = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
//修改端口号
app.Urls.Add("https://*:6022");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
//Cookie策略
app.UseCookiePolicy();
//用户验证
app.UseAuthentication();
//授权
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
代码解析:
(1)添加 AddOpenIdConnect 认证:
builder.Services.AddAuthentication(options =>{
//使用Cookies
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//使用OpenID Connect
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
//客户端ID
options.ClientId = "WebMvcCodeClient";
//客户端密钥
options.ClientSecret = "WebMvcCodeClient00000001";
//IdentityServer4认证服务器地址
options.Authority = "https://localhost:6001";
//响应授权码
options.ResponseType = "code";
//允许Token保存的Cookies中
options.SaveTokens = true;
//权限范围
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//设置允许获取刷新Token
options.Scope.Add("offline_access");
//设置访问的API范围
options.Scope.Add("Customer.Read");
//获取用户的Claims信息
options.GetClaimsFromUserInfoEndpoint = true;
});
代码分析:
-
AddAuthentication:添加身份认证服务
-
options.DefaultScheme=Cookies:我们使用cookie记录本地登录用户
-
options.DefaultChallengeScheme="oidc":需要用户登录,将使用OpenID Connect协议,定义质询(Challenge)方案:质询(Challenge)就是当发现用户没有登录,使用什么方案进行认证
-
AddCookie:添加cookies的处理器
-
AddOpenIdConnect:配置执行OpenID Connect协议的处理器相关参数
-
options.Authority:标识所信赖的token服务地址
-
options.ClientId和options.ClientSecret:标识MVC客户端
-
options.SaveTokens:保存从IdentityServer获取的token至cookie,ture标识ASP.NETCore将会自动存储身份认证session的access和refresh token
-
AddOpenIdConnect("oidc", options =>{...}): 添加名为:“oidc” 的认证方案
-
AddOpenIdConnect()方法已经帮我们处理了授权码模式的流程中的如下步骤:
“客户端再从这个回调的URL中解析到得到code(授权码),再通过code(授权码)向服务器发送请求获取 access_token”,
故我们不必自己去做这些操作,直接在控制器(Controller)中调用如下方法来获取 AccessToken:
//获取accessToken
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
- options.Authority = "https://localhost:6001"; 是认证服务器地址。
(2) 修改端口号
//修改端口号
app.Urls.Add("https://*:6022");
设置客户端地址为:https://localhost:6022
调用受保护的 API 资源
先增文件Controllers/HomeController.cs
,修改代码为:
using Dotnet.WebApi.CodeClient.Models;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Diagnostics;
namespace Dotnet.WebApi.CodeClient.Controllers
{
[Authorize]
public class HomeController : Controller
{
......
/// <summary>
/// 获取API资源。
/// </summary>
/// <returns></returns>
public async Task<IActionResult> ApiData()
{
//获取accessToken
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
//请求API资源
var httpClient = new HttpClient();
//将获取到的AccessToken以Bearer的方案设置在请求头中
httpClient.SetBearerToken(accessToken);
//向API资源服务器请求受保护的API
var data = await httpClient.GetAsync("https://localhost:6011/api/customer/getlist");
if (data.IsSuccessStatusCode)
{
var r = await data.Content.ReadAsStringAsync();
ViewBag.ApiData = r;
}
else
{
ViewBag.ApiData = "获取API数据失败。";
}
return View();
}
......
}
}
代码解析:
(1)在控制器上加入特性:[Authorize],要求用户必须登录。
(1)获取AccessToken:
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
依赖包 Microsoft.AspNetCore.Authentication.OpenIdConnect,AddOpenIdConnect();
方法,已经帮我们处理了授权码模式的流程中的如下步骤:
“客户端再从这个回调的URL中解析到得到code(授权码),再通过code(授权码)向服务器发送请求获取 access_token”,
故我们不必自己去做这些操作,直接在控制器(Controller)中调用如下方法来获取 AccessToken。
(2)创建 HttpClient
对象,设置 AccessToken,访问受保护的Api资源:
//将获取到的AccessToken以Bearer的方案设置在请求头中
httpClient.SetBearerToken(accessToken);
//向API资源服务器请求受保护的API
var data = await httpClient.GetAsync("https://localhost:6011/api/customer/getlist");
退出登录
对于像IdentityServer这样的身份认证服务,清除本地应用程序cookie是不够的。还需要往返于IdentityServer以清除中央单点登录的session。
在控制器中增加退出操作代码:
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
添加视图
Views/Home/Index.cshtml
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div style="margin:20px;">
<a href="/home/userinfo">用户信息</a>
<a href="/home/apidata">调用API</a>
</div>
Views/Home/ApiData.cshtml
@ViewBag.ApiData
@using Microsoft.AspNetCore.Authentication
@{
ViewData["Title"] = "用户信息";
}
<h1>@ViewData["Title"]</h1>
<h2>身份声明</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var c in User.Claims)
{
<tr>
<td>@c.Type</td>
<td>@c.Value</td>
</tr>
}
</table>
<h2>属性</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
{
<tr>
<td>@p.Key</td>
<td>@p.Value</td>
</tr>
}
</table>
Views/Home/UserInfo.cshtml
@using Microsoft.AspNetCore.Authentication
@{
ViewData["Title"] = "用户信息";
}
<h1>@ViewData["Title"]</h1>
<h2>身份声明</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var c in User.Claims)
{
<tr>
<td>@c.Type</td>
<td>@c.Value</td>
</tr>
}
</table>
<h2>属性</h2>
<table class="table table-bordered">
<tr>
<td>类型</td>
<td>值</td>
</tr>
@foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
{
<tr>
<td>@p.Key</td>
<td>@p.Value</td>
</tr>
}
</table>
运行结果
访问客户端主页:https://localhost:6022/
由于在HomeController控制器上加入特性:[Authorize],要求用户必须登录,所以会自动跳转到 IdentityServer4 认证服务器的登录页面:
此时的 Url 为:
https://localhost:6001/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=WebMvcCodeClient&redirect_uri=https://localhost:6022/signin-oidc&response_type=code&scope=openid profile offline_access Customer.Read&code_challenge=YLp1AtbYBkj0v0CbLIG6c5iyf_WfpIOLhfoTaBt07yI&code_challenge_method=S256&response_mode=form_post&nonce=638139540542258598.OWMyZjdlNjgtZDUwOC00NmRjLThhNDEtMmNmN2Y1NjU5ZGEwYTk2NDM3NzQtMWI0Zi00MTE1LWEzMDUtYWNjYzEwNWJmOTRj&state=CfDJ8POHoLGk1JtAslbZG_LqxcfmogxZhhCjTLOAIazSeUfDD_bCaNkvWxAIYi1jYhqfLSF9XLYFeA_rF3OmQ2rsBkhUqpAzs9xI8fFPFoMRwxhvAvqTPKQfziUOTk9lQiqwIbHMeiRRd0ArLOBl0OReoCYpfNce-f7hMvPeJ2sHDmD91SsgaUhqSFe1zKwrlIIz_-bvWe7Mezobk5b_UrOjhPMjbD3xVOZuatVZSsZsy3Pov-4M5WesX_xVUM9-TL9RUQ3wkcM1s9JHSGn4HGh_Uwj3cP5Y7Ri7vdG6ijAyyS0_HDIhqdHjHbN6Ri3hxFe-aZ3rVJdQ8PzyJV6SxnhHYdZrhW0l5FHSlgRKr5h_GSpMiOEcxCELMSTrZDpLe6tYtg&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.15.1.0
这包含了 OAuth2.0 协议的授权码模式规定的相关参数,比如:
- client_id=WebMvcCodeClient
- redirect_uri=https://localhost:6022/signin-oid
- response_type=code
- scope=openid profile offline_access Customer.Read
- code_challenge=YLp1AtbYBkj0v0CbLIG6
- code_challenge_method=S256
- response_mode=form_post
- nonce=638139540542258598......
- state=CfDJ8POHoL...
输入用户名和密码,点击登录,然后跳转到授权页面:
此时的 Url 为:
https://localhost:6001/consent?returnUrl=/connect/authorize/callback?client_id=WebMvcCodeClient&redirect_uri=https%3A%2F%2Flocalhost%3A6022%2Fsignin-oidc&response_type=code&scope=openid%20profile%20offline_access%20Customer.Read&code_challenge=YLp1AtbYBkj0v0CbLIG6c5iyf_WfpIOLhfoTaBt07yI&code_challenge_method=S256&response_mode=form_post&nonce=638139540542258598.OWMyZjdlNjgtZDUwOC00NmRjLThhNDEtMmNmN2Y1NjU5ZGEwYTk2NDM3NzQtMWI0Zi00MTE1LWEzMDUtYWNjYzEwNWJmOTRj&state=CfDJ8POHoLGk1JtAslbZG_LqxcfmogxZhhCjTLOAIazSeUfDD_bCaNkvWxAIYi1jYhqfLSF9XLYFeA_rF3OmQ2rsBkhUqpAzs9xI8fFPFoMRwxhvAvqTPKQfziUOTk9lQiqwIbHMeiRRd0ArLOBl0OReoCYpfNce-f7hMvPeJ2sHDmD91SsgaUhqSFe1zKwrlIIz_-bvWe7Mezobk5b_UrOjhPMjbD3xVOZuatVZSsZsy3Pov-4M5WesX_xVUM9-TL9RUQ3wkcM1s9JHSGn4HGh_Uwj3cP5Y7Ri7vdG6ijAyyS0_HDIhqdHjHbN6Ri3hxFe-aZ3rVJdQ8PzyJV6SxnhHYdZrhW0l5FHSlgRKr5h_GSpMiOEcxCELMSTrZDpLe6tYtg&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.15.1.0
点击【Yes, Allow】进行授权。
授权成功后,返回客户端页面,
从授权成功后,返回客户端页面这期间,其实还经历两个步骤,
第一步:IdentityServer4 认证服务器回调 https://localhost:6022/signin-oidc
:
返回的Url为:
https://localhost:6022/signin-oidc?code=.....&
这个过程太快了,没法看到链接,但可以肯定的是,返回的Url中包含了 OAuth2.0 协议的相关参数,其中就包括名为:code的授权码的参数。
第二步:使用code(授权码)向 IdentityServer认证服务器发送请求获取AccessToken.
以上这两个步骤在调用.AddOpenIdConnect()
方法后,相关中间件已经帮我们处理了,包括:
- 获取accessToken
- 为认证方案 “oidc”进行登录
- 保存 accessToken 到 Cookie 中
,故我们不必自己去做这些操作,直接在控制器(Controller)中调用如下方法来获取 AccessToken:
//获取accessToken
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
最后返回到主页,
点击【调用API】按钮,我们可以看到通过 AccessToken 访问资源服务受保护的数据:
点击【用户信息】按钮,我们可以看到用户信息:
其中
access_token 为:
eyJhbGciOiJSUzI1NiIsImtpZCI6IkJBRUUyOEI1NkFDNTFFMjI0RTgxQjE0OTI2RTU5REEwIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NzgzNTkyMjgsImV4cCI6MTY3ODM2MjgyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwMSIsImF1ZCI6IkN1c3RvbWVyQVBJUmVzb3VyY2UiLCJjbGllbnRfaWQiOiJXZWJNdmNDb2RlQ2xpZW50Iiwic3ViIjoiMDAwMDMiLCJhdXRoX3RpbWUiOjE2NzgzNTkxODcsImlkcCI6ImxvY2FsIiwianRpIjoiNEUyQjc0REIzMDRDM0EzQUQ1RDRCQzc1MEQxRjNGNkQiLCJzaWQiOiJGMEIwRDQyNkM0NTI3Qzc4QkJFODBCMDhFQTg2RkFGRSIsImlhdCI6MTY3ODM1OTIyOCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsIkN1c3RvbWVyLlJlYWQiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicHdkIl19.fmeaQgJ14WCRkgE5sZy6X_K3XkavVRfyqVwyK--
id_token 为:
eyJhbGciOiJSUzI1NiIsImtpZCI6IkJBRUUyOEI1NkFDNTFFMjI0RTgxQjE0OTI2RTU5REEwIiwidHlwIjoiSldUIn0.eyJuYmYiOjE2NzgzNTkyMjgsImV4cCI6MTY3ODM1OTUyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwMSIsImF1ZCI6IldlYk12Y0NvZGVDbGllbnQiLCJub25jZSI6IjYzODEzOTU1OTc2NTM5Mzc2Ny5ZekU1TlRBMU5tWXRNbU5rTkMwMFlXSTFMV0l3WmpFdFltTTFPR0kyTmpVNFl6STBOVFF5WmpZMk0yUXROakUwT1MwME5tWTRMVGsyTmpndFltUmxNR1V6TVRWa01EQTMiLCJpYXQiOjE2NzgzNTkyMjgsImF0X2hhc2giOiJUaFVlb25EZFVnNTNLWlU5SEdjSE9BIiwic19oYXNoIjoiZHZPNEZXZko5SjJLRjMzckRnYlJsQSIsInNpZCI6IkYwQjBENDI2QzQ1MjdDNzhCQkU4MEIwOEVBODZGQUZFIiwic3ViIjoiMDAwMDMiLCJhdXRoX3RpbWUiOjE2NzgzNTkxODcsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.D5dgKxvqXhdnoAIrrQgVbX8B8fSMxdgQCQlXToYMrYEanO2dPA2MPoOrUGrgJXiCcNUZ1Ot9eU19GqLx86ZM-pXqpf_dbDOIPTeqPi07s9Rkjc7Ez7vRY7XiXG93Dn602Egh1clwFwaw9D20YIXGP8cbduDsT1qqk9dp8flaw1bKH2FXuUr-YdOuw9FtapSKk9b1eaD9dPAAyG7dZPIzRrMoVdYITzM0ZZiYTnTiaTTdZO8Xool9l7ByoIz7nHedsBht1bD0hIqYckMNsadEeVZ6tpzxJAGWYaf0gl5mlvlkxrYSerGoLdHLxJZcN6263DBq2T70wdelZst4Vycelg
参考资料
【One by One系列】IdentityServer4(四)授权码流程
使用Identity Server 4建立Authorization Server (4)