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)
IdentityServer4 IdentityServer 模式identityserver4 identityserver密码 模式 identityserver4 identityserver客户端 模式 identityserver4 identityserver模式 identityserver4 identityserver4 identityserver tokenrequestvalidator identityserver4 identityserver identityserver4 identityserver ocelot net6 identityserver4 identityserver net v4 identityserver4 identityserver证书 问题 identityserver4 identityserver客户端 客户