ASP.NET Core中提供了多种身份证认证模式,几种常见的身份认证模式有以下几种。本文将详细介绍一下OpenID Connect身份认证在ASP.NET Core中如何使用(三方以Gitee为例)。
认证模式 | 介绍 |
Cookie | 最常见的身份认证方式之一。用户登录成功后,服务器会生成一个加密的 Cookie 并发送给客户端,客户端在后续请求中携带该 Cookie 来验证用户身份。 |
JWT | JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在网络上安全地传输声明。JWT 认证通过在客户端和服务器之间传递加密的 Token 来验证用户身份。 |
OAuth | 一种开放标准,用于授权第三方应用程序访问用户数据。ASP.NET Core 提供了 OAuth 认证机制,允许应用程序通过 OAuth 协议与第三方身份提供者进行集成。 |
OpenID Connect | 建立在 OAuth 2.0 协议之上的身份认证协议,用于验证用户身份。ASP.NET Core 提供了对 OpenID Connect 的支持,可以与支持 OpenID Connect 的身份提供者集成。 |
Windows | ASP.NET Core 支持 Windows 身份认证,允许用户使用他们的 Windows 凭据登录到应用程序。 |
OpenID Connect
OpenID Connect(ODIC)是基于OAuth2.0之上的扩展,在OAuth2.0之上新增了身份验证的功能。
ODIC的应用场景通常如下:
ODIC由一个必选的核心规范以及多个可选的扩展规范组成,通常会支持以下规范:
- Core:必选。定义OIDC的核心功能,在OAuth 2.0之上构建身份认证,以及如何使用Claims来传递用户的信息。
- Discovery:可选。发现服务,使客户端可以动态的获取OIDC服务相关的元数据描述信息(比如支持那些规范,接口地址是什么等等)。
- Dynamic Registration:可选。动态注册服务,使客户端可以动态的注册到OIDC的OP(这个缩写后面会解释)。
- OAuth 2.0 Multiple Response Types:可选。针对OAuth2的扩展,提供几个新的response_type。
- OAuth 2.0 Form Post Response Mode:可选。针对OAuth2的扩展,OAuth2回传信息给客户端是通过URL的querystring和fragment这两种方式,这个扩展标准提供了一基于form表单的形式把数据post给客户端的机制。
- Session Management:可选。Session管理,用于规范OIDC服务如何管理Session信息。
- Front-Channel Logout:可选。基于前端的注销机制,使得RP(这个缩写后面会解释)可以不使用OP的iframe来退出。
- Back-Channel Logout:可选。基于后端的注销机制,定义了RP和OP直接如何通信来完成注销。
官方OIDC组成结构图如下:
- EU:End User:一个人类用户。
- RP:Relying Party ,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;
- OP:OpenID Provider,有能力提供EU认证的服务(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息;
- ID Token:JWT格式的数据,包含EU身份认证的信息。
- UserInfo Endpoint:用户信息接口(受OAuth2保护),当RP使用Access Token访问时,返回授权用户的信息,此接口必须使用HTTPS。
OIDC工作流程
OIDC的流程由以下5个步骤构成:
- RP发送一个认证请求给OP;
- OP对EU进行身份认证,然后提供授权;
- OP把ID Token和Access Token(需要的话)返回给RP;
- RP使用Access Token发送一个请求UserInfo EndPoint;
- UserInfo EndPoint返回EU的Claims。
AuthN=Authentication,表示认证;AuthZ=Authorization,代表授权。
具体流程请查看Core规范文档提供的工作流程图:
授权类型
OIDC与OAuth2一样中定义了四种授权方式:
授权码模式(Authorization Code Grant)
客户端重定向用户到授权服务器,用户登录并授权后,授权服务器将重定向用户回客户端,并提供授权码。客户端使用授权码向授权服务器请求访问令牌。
适用于 Web 应用程序,客户端能够保护授权码的安全性。
Demo(由identityserver4+Microsoft.AspNetCore.Authentication实现)
来自identityserver4文档:使用 ASP.NET Core 的交互式应用程序 — IdentityServer4 1.0.0 文档。
项目结构如下:
授权中间件生命周期如下:
授权中间件(Microsoft.AspNetCore.Authentication.Gitee)————参考ASP.NET Core源码实现
- 在项目文件中添加Microsoft.AspNetCore.App引用。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
- 添加Gitee身份认证默认参数
GiteeDefaults.cs
namespace Microsoft.AspNetCore.Authentication.Gitee;
/// <summary>
/// Gitee身份认证默认参数
/// </summary>
public static class GiteeDefaults
{
/// <summary>
/// Gitee默认身份认证方案
/// </summary>
public const string AuthenticationScheme = "Gitee";
/// <summary>
/// Gitee默认身份认证显示名称
/// </summary>
public static readonly string DisplayName = "Gitee";
/// <summary>
/// Gitee进行身份验证的API地址
/// </summary>
/// <remarks>
/// 详情查看文档 <see href="https://gitee.com/api/v5/oauth_doc"/>.
/// </remarks>
public static readonly string AuthorizationEndpoint = "https://gitee.com/oauth/authorize";
/// <summary>
/// Gitee获取访问令牌的API地址
/// </summary>
public static readonly string TokenEndpoint = "https://gitee.com/oauth/token";
/// <summary>
/// Gitee获取用户信息的API地址
/// </summary>
public static readonly string UserInformationEndpoint = "https://gitee.com/api/v5/user";
}
- 添加Gitee默认配置
GiteeOptions.cs
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace Microsoft.AspNetCore.Authentication.Gitee;
/// <summary>
/// GiteeHandler配置
/// </summary>
public class GiteeOptions : OAuthOptions
{
/// <summary>
/// 初始化一个新实例
/// </summary>
public GiteeOptions()
{
//登陆后回调地址
CallbackPath = new PathString("/signin");
AuthorizationEndpoint = GiteeDefaults.AuthorizationEndpoint;
TokenEndpoint = GiteeDefaults.TokenEndpoint;
UserInformationEndpoint = GiteeDefaults.UserInformationEndpoint;
Scope.Add("user_info");
Scope.Add("projects");
Scope.Add("pull_requests");
Scope.Add("issues");
Scope.Add("notes");
Scope.Add("keys");
Scope.Add("hook");
Scope.Add("groups");
Scope.Add("gists");
Scope.Add("enterprises");
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
}
}
- 添加授权中间件扩展方法
GiteeExtensions.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Gitee;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods to configure Microsoft Account OAuth authentication.
/// </summary>
public static class GiteeExtensions
{
/// <summary>
/// Adds Microsoft Account OAuth-based authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="GiteeDefaults.AuthenticationScheme"/>.
/// <para>
/// Microsoft Account authentication allows application users to sign in with their work, school, or personal Microsoft account.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddGitee(this AuthenticationBuilder builder)
=> builder.AddGitee(GiteeDefaults.AuthenticationScheme, _ => { });
/// <summary>
/// Adds Microsoft Account OAuth-based authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="GiteeDefaults.AuthenticationScheme"/>.
/// <para>
/// Microsoft Account authentication allows application users to sign in with their work, school, or personal Microsoft account.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions">A delegate to configure <see cref="GiteeOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddGitee(this AuthenticationBuilder builder, Action<GiteeOptions> configureOptions)
=> builder.AddGitee(GiteeDefaults.AuthenticationScheme, configureOptions);
/// <summary>
/// Adds Microsoft Account OAuth-based authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="GiteeDefaults.AuthenticationScheme"/>.
/// <para>
/// Microsoft Account authentication allows application users to sign in with their work, school, or personal Microsoft account.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">A delegate to configure <see cref="GiteeOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddGitee(this AuthenticationBuilder builder, string authenticationScheme, Action<GiteeOptions> configureOptions)
=> builder.AddGitee(authenticationScheme, GiteeDefaults.DisplayName, configureOptions);
/// <summary>
/// Adds Microsoft Account OAuth-based authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="GiteeDefaults.AuthenticationScheme"/>.
/// <para>
/// Microsoft Account authentication allows application users to sign in with their work, school, or personal Microsoft account.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="displayName">A display name for the authentication handler.</param>
/// <param name="configureOptions">A delegate to configure <see cref="GiteeOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddGitee(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<GiteeOptions> configureOptions)
=> builder.AddOAuth<GiteeOptions, GiteeHandler>(authenticationScheme, displayName, configureOptions);
}
- 添加Gitee基于OAuth的身份验证的身份验证处理程序
GiteeHandler.cs
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
namespace Microsoft.AspNetCore.Authentication.Gitee;
/// <summary>
/// Gitee基于OAuth的身份验证的身份验证处理程序
/// </summary>
public class GiteeHandler : OAuthHandler<GiteeOptions>
{
/// <summary>
/// 初始化一个新实例
/// </summary>
/// <inheritdoc />
[Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")]
public GiteeHandler(IOptionsMonitor<GiteeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
/// <summary>
/// 初始化一个新实例
/// </summary>
/// <inheritdoc />
public GiteeHandler(IOptionsMonitor<GiteeOptions> options, ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
{ }
}
授权服务器(IdentityServer)
- 引用IdentityServer4 Nuget包。(版本号:4.1.2)
- 添加Identity4 配置
Config.cs
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
namespace IdentityServer;
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "verification",
UserClaims = new List<string>
{
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified
}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope(name: "api1", displayName: "My API")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
},
// interactive ASP.NET Core Web App
new Client
{
ClientId = "web",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
// 注意:需要改成web client的地址
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
// 注意:需要改成web client的地址
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"verification"
}
}
};
}
- 添加自定义授权的Gitee身份认证配置
Program.cs
using IdentityServer;
using IdentityServer4;
using IdentityServerHost.Quickstart.UI;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System.Net.Http.Headers;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
//添加身份认证
builder.Services.AddIdentityServer()
#region 临时密钥,调试或开发时使用
.AddDeveloperSigningCredential()
#endregion
#region X.509证书
//生产环境推荐使用证书方式来配置签名凭证,path_to_cert.pfx
//.AddSigningCredential(new X509Certificate2("path_to_cert.pfx", "certificate_password"))
#endregion
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
//添加授权服务
builder.Services.AddAuthentication()
.AddOpenIdConnect("oidc", "Demo IdentityServer", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "https://demo.duendesoftware.com";
options.ClientId = "interactive.confidential";
options.ClientSecret = "secret";
options.ResponseType = OpenIdConnectResponseType.Code;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
})
.AddGitee("Gitee", "Demo Gitee", options =>
{
options.ClientId = "43xxxxxxxxxxxxxxxxxxxxxx";
options.ClientSecret = "e6be78689cxxxxxxxxxxxxxxxxxxxx";
options.CallbackPath = new PathString("/callback");
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SaveTokens = true;
//凭据创建事件
options.Events.OnCreatingTicket = async context =>
{
#region 获取用户信息
var request = new HttpRequestMessage(HttpMethod.Get, options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"An error occurred when retrieving Gitee user information ({response.StatusCode}). Please check if the authentication information is correct.");
}
response.EnsureSuccessStatusCode();
using (var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()))
{
context.RunClaimActions(user.RootElement);
}
#endregion
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseStaticFiles();
app.UseRouting();
//启用身份认证服务
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute().RequireAuthorization();
app.Run();
Web客户端(WebClient)
直接使用ID4的Demo,不需要修改
隐式授权(Implicit Grant)
类似于授权码授权,但客户端直接从授权服务器获取访问令牌,而不是通过授权码交换。
适用于浏览器或移动应用,无法安全地存储客户端密钥。
Demo(由identityserver4实现)
来自identityserver4文档:ASP.NET Core 和 API 访问 — IdentityServer4 1.0.0 文档。
项目结构如下:
授权服务器(IdentityServer)
- 引用IdentityServer4 Nuget包。(版本号:4.1.2)
- 添加Identity4 配置
Config.cs
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
namespace IdentityServer;
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "verification",
UserClaims = new List<string>
{
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified
}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope(name: "api1", displayName: "My API")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
},
// interactive ASP.NET Core Web App
new Client
{
ClientId = "web",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "https://localhost:7132/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:7132/signout-callback-oidc" },
//启用对刷新令牌的支持
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
- 添加Identity4服务
Program.cs
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
namespace IdentityServer;
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "verification",
UserClaims = new List<string>
{
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified
}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope(name: "api1", displayName: "My API")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
},
// interactive ASP.NET Core Web App
new Client
{
ClientId = "web",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "https://localhost:7132/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:7132/signout-callback-oidc" },
//启用对刷新令牌的支持
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
资源服务器(Api)
- 引用Microsoft.AspNetCore.Authentication.JwtBearer Nuget包。(版本号:8.0.8)
- 添加身份认证服务
Program.cs
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
//添加身份认证
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:7055";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
- 在控制器中添加需要授权才能访问的接口
IdentityController.cs
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers
{
[Route("identity")]
[ApiController]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
}
- 到此客户端授权模式的两个服务器已都搭建完毕。可通过ApiFox或Postman等API工具进行测试。
Web客户端(WebClient)
- 引用Microsoft.AspNetCore.Authentication.OpenIdConnect Nuget包。(版本号:8.0.8)
- 引用Newtonsoft.Json Nuget包。(版本号:13.0.3)
- 添加身份认证服务
Program.cs
using Microsoft.AspNetCore.Authentication;
using System.IdentityModel.Tokens.Jwt;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:7055";
options.ClientId = "web";
options.ClientSecret = "secret";
options.ResponseType = "code";
//保存令牌
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.ClaimActions.MapJsonKey("email_verified", "email_verified");
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false; // Don't rename claim types
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute().RequireAuthorization();
app.Run();
- Home控制器添加CallApi控制器,并将Index重定向至该方法
HomeController.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MvcClient.Models;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Net.Http.Headers;
namespace MvcClient.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return RedirectToAction("CallApi");
}
public async Task<IActionResult> CallApi()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetStringAsync("http://localhost:5200/identity");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
- 添加json.cshtml页面
json.cshtml
<pre>@ViewBag.Json</pre>
- 登陆(账号密码均为:bob)后结果如图:
密码模式(Resource Owner Password Credentials Grant)
用户直接向客户端提供用户名和密码,客户端使用这些凭据向授权服务器请求访问令牌。
适用于受信任的应用程序,如原生移动应用或受限制的服务。
服务器授权模式Demo中已包含密码模式。
客户端授权模式(Client Credentials Grant)
客户端使用自身的凭证(客户端 ID 和客户端密钥)向授权服务器请求访问令牌,而不涉及用户。
适用于机器对机器通信,或客户端需要访问自己的资源。
Demo(由identityserver4实现)
来自identityserver4文档:使用客户端凭据保护 API — IdentityServer4 1.0.0 文档。
项目结构如下:
授权服务器(IdentityServer)
- 引用IdentityServer4 Nuget包。(版本号:4.1.2)
- 添加Identity4 配置
Config.cs
using IdentityServer4.Models;
namespace IdentityServer
{
/// <summary>
/// Identity4 配置
/// </summary>
public static class Config
{
/// <summary>
/// API允许访问范围
/// </summary>
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
/// <summary>
/// 客户端
/// </summary>
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
}
}
添加Identity4服务
Program.cs
using IdentityServer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
//添加Identity4服务
builder.Services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
//启用Identity4中间件
app.UseIdentityServer();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
通过访问发现文档(地址为:http://localhost:5280/.well-known/openid-configuration,http://localhost:5280为你的授权服务器地址)。来确认配置是否成功。配置成功结果如图:
资源服务器(Api)
- 引用Microsoft.AspNetCore.Authentication.JwtBearer Nuget包。(版本号:8.0.8)
- 添加身份认证服务
Program.cs
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
//添加身份认证
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
//若不设置为false则必须使用https
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:5280";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
- 在控制器中添加需要授权才能访问的接口
IdentityController.cs
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers
{
[Route("identity")]
[ApiController]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
}
- 到此客户端授权模式的两个服务器已都搭建完毕。可通过ApiFox或Postman等API工具进行测试。
客户端(Client)
- 引用IdentityModel Nuget包。(版本号:7.0.0)
- 引用Newtonsoft.Json Nuget包。(版本号:13.0.3)
- 向授权服务器请求访问Token
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5280");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// 请求token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
- 携带访问Token访问资源服务器
// 调用 api
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
Console.WriteLine(apiClient.DefaultRequestHeaders.ToString());
var response = await apiClient.GetAsync("http://localhost:5200/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
- 访问结果
- 若不携带Token,则请求不到结果