zoukankan      html  css  js  c++  java
  • ASP.NET Core3.1使用IdentityServer4中间件系列随笔(四):创建使用[ResourceOwnerPassword-资源所有者密码凭证]授权模式的客户端

    配套源码:https://gitee.com/jardeng/IdentitySolution

    本篇将创建使用[ResourceOwnerPassword-资源所有者密码凭证]授权模式的客户端,来对受保护的API资源进行访问。

    接上一篇项目,在IdentityServer项目Config.cs中添加一个客户端

    /// 资源所有者密码凭证(ResourceOwnerPassword)
    ///     Resource Owner其实就是User,所以可以直译为用户名密码模式。
    ///     密码模式相较于客户端凭证模式,多了一个参与者,就是User。
    ///     通过User的用户名和密码向Identity Server申请访问令牌。
    new Client
    { 
        ClientId = "client1",
        AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
        ClientSecrets = { new Secret("secret".Sha256()) },
        AllowedScopes = { "api1" }
    }
    View Code

    再添加一个用户的集合(测试数据来自IdentityServer官方)。

    完整的Config.cs代码

    using System.Collections.Generic;
    using System.Security.Claims;
    
    using IdentityModel;
    
    using IdentityServer4.Models;
    using IdentityServer4.Test;
    
    namespace IdentityServer
    {
        /// <summary>
        /// IdentityServer资源和客户端配置文件
        /// </summary>
        public static class Config
        {
            /// <summary>
            /// API资源集合
            ///     如果您将在生产环境中使用此功能,那么给您的API取一个逻辑名称就很重要。
            ///     开发人员将使用它通过身份服务器连接到您的api。
            ///     它应该以简单的方式向开发人员和用户描述您的api。
            /// </summary>
            public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("api1", "My API") };
    
            /// <summary>
            /// 客户端集合
            /// </summary>
            public static IEnumerable<Client> Clients =>
                new List<Client>
                {
                    /// 客户端模式(Client Credentials)
                    ///     可以将ClientId和ClientSecret视为应用程序本身的登录名和密码。
                    ///     它将您的应用程序标识到身份服务器,以便它知道哪个应用程序正在尝试与其连接。
                    new Client
                    { 
                        //客户端标识
                        ClientId = "client",
                        //没有交互用户,使用clientid/secret进行身份验证,适用于和用户无关,机器与机器之间直接交互访问资源的场景。
                        AllowedGrantTypes = GrantTypes.ClientCredentials,
                        //认证密钥
                        ClientSecrets = { new Secret("secret".Sha256()) },
                        //客户端有权访问的作用域
                        AllowedScopes = { "api1" }
                    },
                    /// 资源所有者密码凭证(ResourceOwnerPassword)
                    ///     Resource Owner其实就是User,所以可以直译为用户名密码模式。
                    ///     密码模式相较于客户端凭证模式,多了一个参与者,就是User。
                    ///     通过User的用户名和密码向Identity Server申请访问令牌。
                    new Client
                    {
                        ClientId = "client1",
                        AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                        ClientSecrets = { new Secret("secret".Sha256()) },
                        AllowedScopes = { "api1" }
                    }
                };
    
            /// <summary>
            /// 用户集合
            /// </summary>
            public static List<TestUser> Users =>
                new List<TestUser>
                {
                    new TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
                        Claims =
                        {
                            new Claim(JwtClaimTypes.Name, "Alice Smith"),
                            new Claim(JwtClaimTypes.GivenName, "Alice"),
                            new Claim(JwtClaimTypes.FamilyName, "Smith"),
                            new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
                            new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                            new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                            new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
                        }
                    },
                    new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
                        Claims =
                        {
                            new Claim(JwtClaimTypes.Name, "Bob Smith"),
                            new Claim(JwtClaimTypes.GivenName, "Bob"),
                            new Claim(JwtClaimTypes.FamilyName, "Smith"),
                            new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
                            new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                            new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
                            new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
                            new Claim("location", "somewhere")
                        }
                    }
                };
        }
    }
    View Code

    我们使用Postman来获取ResourceOwnerPassword这种模式的AcceccToken

    与上一种 Client Credentials 模式不同的是 client_id 使用 client1,grant_type 由原来的 client_credentials 改为 password,多了 usernamepassword 两个参数,使用用户名密码 alice / alice 来登录

    2、创建一个名为 ResourceOwnerPasswordConsoleApp 的控制台客户端应用。

     创建完成后的项目截图

     

    3、添加nuget包:IdentityModel

    在Program.cs编写代码

    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    using IdentityModel.Client;
    
    using Newtonsoft.Json.Linq;
    
    namespace ResourceOwnerPasswordConsoleApp
    {
        class Program
        {
            static async Task Main(string[] args)
            {
                bool verifySuccess = false;
                TokenResponse tokenResponse = null;
                while (!verifySuccess)
                {
                    Console.WriteLine("请输入用户名:");
                    string userName = Console.ReadLine();
                    Console.WriteLine("请输入密码:");
                    string password = Console.ReadLine();
    
                    //discovery endpoint - 发现终结点
                    HttpClient client = new HttpClient();
                    DiscoveryDocumentResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
                    if (disco.IsError)
                    {
                        Console.WriteLine($"[DiscoveryDocumentResponse Error]: {disco.Error}");
                        return;
                    }
    
                    //request assess token - 请求访问令牌
                    tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
                    {
                        Address = disco.TokenEndpoint,
                        ClientId = "client1",
                        ClientSecret = "secret",
                        Scope = "api1",
                        UserName = userName,
                        Password = password
                    });
                    if (tokenResponse.IsError)
                    {
                        //ClientId 与 ClientSecret 错误,报错:invalid_client
                        //Scope 错误,报错:invalid_scope
                        //UserName 与 Password 错误,报错:invalid_grant
                        string errorDesc = tokenResponse.ErrorDescription;
                        if (string.IsNullOrEmpty(errorDesc)) errorDesc = "";
                        if (errorDesc.Equals("invalid_username_or_password"))
                        {
                            Console.WriteLine("用户名或密码错误,请重新输入!");
                        }
                        else
                        {
                            Console.WriteLine($"[TokenResponse Error]: {tokenResponse.Error}, [TokenResponse Error Description]: {errorDesc}");
                        }
                        Console.WriteLine("");
                        continue;
                    }
                    else
                    {
                        Console.WriteLine("");
                        Console.WriteLine($"Access Token: {tokenResponse.AccessToken}");
                        verifySuccess = true;
                    }
                }
    
                //call API Resource - 访问API资源
                HttpClient apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse?.AccessToken);
                HttpResponseMessage response = await apiClient.GetAsync("http://localhost:6000/weatherforecast");
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"API Request Error, StatusCode is : {response.StatusCode}");
                }
                else
                {
                    string content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine("");
                    Console.WriteLine($"Result: {JArray.Parse(content)}");
                }
    
                Console.ReadKey();
            }
        }
    }

    用户名密码错误的话,会一直提示重新输入

    我们使用用户名密码 alice / alice 进行登录

    可以看到,成功获取到AccessToken,并使用AccessToken访问到受保护的API获取到结果。

  • 相关阅读:
    P1829 [国家集训队]Crash的数字表格 / JZPTAB 莫比乌斯反演
    Luogu P1447 [NOI2010]能量采集 数论??欧拉
    Luogu P2522 [HAOI2011]Problem b 莫比乌斯反演
    Luogu P2257 YY的GCD 莫比乌斯反演
    [笔记] 数论函数小记
    [笔记] 线性筛小记
    Luogu P1092 虫食算 爆搜
    Luogu P1066 2^k进制数 组合数学
    Luogu P1641 [SCOI2010]生成字符串 组合数学
    Luogu P2532 [AHOI2012]树屋阶梯 卡特兰数
  • 原文地址:https://www.cnblogs.com/jardeng/p/12776317.html
Copyright © 2011-2022 走看看