首页 > 其他分享 >IdentityServer4:授权码模式

IdentityServer4:授权码模式

时间:2023-11-21 17:22:05浏览次数:33  
标签:app 模式 https new 授权 options IdentityServer4 客户端

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, "[email protected]"),
                            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, "[email protected]"),
                            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);

(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.OpenIdConnectAddOpenIdConnect();方法,已经帮我们处理了授权码模式的流程中的如下步骤:
“客户端再从这个回调的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)

转载至:https://www.cnblogs.com/easy5weikai/p/17199370.html  

标签:app,模式,https,new,授权,options,IdentityServer4,客户端
From: https://www.cnblogs.com/dingdingyiyi/p/17847064.html

相关文章

  • springboot移动端授权登录请求接口说明
    使用系统内部演示代码,在附件下载方便统一管理用户方便在线用户监控一处编写、处处可用统一鉴权方式1.新增角色、用户组【若已分配可跳过】角色管理-新增专门用于移动等模块-不分配任何后台菜单【DZDS已有】2.新增、修改用户在业务模块添加、修改用户信息,需要同步到sys_user中......
  • 观察者模式
    目录观察者模式概述结构案例实现优缺点使用场景JDK中提供的实现观察者模式概述定义:又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。......
  • 状态模式
    目录状态模式概述结构案例实现优缺点使用场景状态模式概述【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可......
  • 访问者模式
    目录访问者模式概述结构案例实现优缺点使用场景扩展访问者模式概述定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。结构访问者模式包含以下主要角色:抽象访问者(Visitor)角色:定义了对每一个元素(Element)访......
  • 迭代器模式
    目录迭代器模式概述结构案例实现优缺点使用场景JDK源码解析迭代器模式概述定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。结构迭代器模式主要包含以下角色:抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。......
  • 中介者模式
    目录中介者模式概述结构案例实现优缺点使用场景中介者模式概述一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将......
  • 解释器模式
    目录解释器模式概述结构案例实现优缺点使用场景解释器模式概述如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。//用于两个整数相加publicstaticintadd(inta,intb){returna+b;}//用于两个整数相加public......
  • 备忘录模式
    目录备忘录模式概述结构案例实现“白箱”备忘录模式“黑箱”备忘录模式优缺点使用场景备忘录模式概述定义:备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提......
  • FinClip解决繁琐的第三方微信授权登录流程
    用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。微信的授权登录在日常应用中应用的非常广泛,越来越多的平台支持用户使用微信进行授权第三方登录.使用微信授权登录有哪些优势/好处;用户量足够大,基本所有用户都会有微信,登录......
  • 行为型模式-模板方法模式
    1什么是模板方法模式模板方法模式是一种行为设计模式,它定义了一个算法的骨架,将一些步骤的具体实现延迟到子类中。这样可以在不改变算法结构的情况下,允许子类根据自身的需求来实现特定的步骤。模板方法模式通常由一个抽象基类提供一个模板方法,该方法定义了算法的骨架,并调用一系......