.NET8入门:10.身份验证(OpenID Connect)

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由一个必选的核心规范以及多个可选的扩展规范组成,通常会支持以下规范:

  1. Core:必选。定义OIDC的核心功能,在OAuth 2.0之上构建身份认证,以及如何使用Claims来传递用户的信息。
  2. Discovery:可选。发现服务,使客户端可以动态的获取OIDC服务相关的元数据描述信息(比如支持那些规范,接口地址是什么等等)。
  3. Dynamic Registration:可选。动态注册服务,使客户端可以动态的注册到OIDC的OP(这个缩写后面会解释)。
  4. OAuth 2.0 Multiple Response Types:可选。针对OAuth2的扩展,提供几个新的response_type。
  5. OAuth 2.0 Form Post Response Mode:可选。针对OAuth2的扩展,OAuth2回传信息给客户端是通过URL的querystring和fragment这两种方式,这个扩展标准提供了一基于form表单的形式把数据post给客户端的机制。
  6. Session Management:可选。Session管理,用于规范OIDC服务如何管理Session信息。
  7. Front-Channel Logout:可选。基于前端的注销机制,使得RP(这个缩写后面会解释)可以不使用OP的iframe来退出。
  8. Back-Channel Logout:可选。基于后端的注销机制,定义了RP和OP直接如何通信来完成注销。

官方OIDC组成结构图如下:

OIDC术语

  1. EU:End User:一个人类用户。
  2. RP:Relying Party ,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;
  3. OP:OpenID Provider,有能力提供EU认证的服务(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息;
  4. ID Token:JWT格式的数据,包含EU身份认证的信息。
  5. UserInfo Endpoint:用户信息接口(受OAuth2保护),当RP使用Access Token访问时,返回授权用户的信息,此接口必须使用HTTPS。

OIDC工作流程

OIDC的流程由以下5个步骤构成:

  1. RP发送一个认证请求给OP;
  2. OP对EU进行身份认证,然后提供授权;
  3. OP把ID Token和Access Token(需要的话)返回给RP;
  4. RP使用Access Token发送一个请求UserInfo EndPoint;
  5. 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>

  1. 登陆(账号密码均为: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,则请求不到结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值