控制台程序httpclient使用IdentityServer4的授权码模式获取token


系统使用IdentityServer4做鉴权,客户端是WPF,打算使用httpclient获取授权码模式的token,现做一个控制台测试例子。

获取token代码

新建一个AuthorizationCodeLogin类

using IdentityModel;
using IdentityModel.Client;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace ClientAuthorizationCode
{
    public class AuthorizationCodeLogin
    {
        public async Task<string?> GetTokenAsync()
        {
            var webProxy = new WebProxy(new Uri("http://127.0.0.1:8080"));//代理,用burp suite进行截断调试
            var baseAddress = new Uri("https://localhost:5001");

            var cookieContainer = new CookieContainer();
            var handler = new HttpClientHandler()
            {
                //Proxy = webProxy,
                //UseProxy = true,
                UseCookies = true,
                CookieContainer = cookieContainer,
            };
            var client = new HttpClient(handler);


            var disco = await client.GetDiscoveryDocumentAsync(baseAddress.AbsoluteUri);
            if (disco.IsError)
            {
                Console.WriteLine(disco.Error);
                return null;
            }

            #region 创建验证地址
            var nonce = Convert.ToBase64String(CryptoRandom.CreateRandomKey(64));
            var codeVerifier = GenerateCodeVerifier();
            var codeChallenge = GenerateCodeChallenge(codeVerifier);

            var ru = new RequestUrl(disco.AuthorizeEndpoint);
            var url = ru.CreateAuthorizeUrl(
                clientId: "apiClientCode",
                responseType: "code",
                redirectUri: "http://localhost:5002/signin-oidc",
                nonce: nonce,
                codeChallenge: codeChallenge,
                codeChallengeMethod: "S256",
                scope: "api1");

            Console.WriteLine(url);

            #endregion


            #region 获取真正的登录地址,以及登录参数和Cookie
            var response1 = await client.GetAsync(url);

            if(!response1.IsSuccessStatusCode)
            {
                Console.WriteLine("获取真正的登录地址" + response1.StatusCode);
                return null;
            }
            var result = await response1.Content.ReadAsStringAsync();

            string temp = "<input name=\"__RequestVerificationToken\" type=\"hidden\" value=\"";
            string temp1 = "\" /><input name=\"RememberLogin\"";
            int startIndex = result.IndexOf(temp);
            int endIndex = result.IndexOf(temp1);
            int length = endIndex - startIndex - temp.Length;
            string requestVerificationToken = result.Substring(startIndex + temp.Length, length);
            Console.WriteLine(requestVerificationToken);


            string temp2 = "<input type=\"hidden\" id=\"ReturnUrl\" name=\"ReturnUrl\" value=\"";
            string temp3 = "code_challenge_method=S256";
            int startIndex1 = result.IndexOf(temp2) + temp2.Length;
            int endIndex1 = result.IndexOf(temp3);
            int length1 = endIndex1 - startIndex1 + temp3.Length;
            string returnUrl = result.Substring(startIndex1, length1).Replace("amp;", "");

            Console.WriteLine(returnUrl);
            var cookies = response1.Headers.GetValues(HeaderNames.SetCookie).ToList();

            var cookieTemp = cookies[0].Split(';');
            var cookieTemp1 = cookieTemp[0].Split('=');

            #endregion


            #region 登录,获取code
            handler.CookieContainer.Add(baseAddress, new Cookie(cookieTemp1[0], cookieTemp1[1]));
            //var client1 = new HttpClient(handler);
            var formContent = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("Username", "alice"),
                new KeyValuePair<string, string>("Password", "Pass123$"),
                new KeyValuePair<string, string>("ReturnUrl", returnUrl),
                new KeyValuePair<string, string>("button", "login"),
                new KeyValuePair<string, string>("__RequestVerificationToken", requestVerificationToken),
                new KeyValuePair<string, string>("RememberLogin", "false"),
            });

            var response = await client.PostAsync(response1.RequestMessage.RequestUri, formContent);

            Console.WriteLine(response.Headers.Location.AbsoluteUri);
            Console.WriteLine(response.IsSuccessStatusCode);
            Console.WriteLine(response.StatusCode);
            if (!(response.StatusCode == HttpStatusCode.Found))
            {
                Console.WriteLine("获取Code失败"+response.StatusCode);
                return null;
            }

            //根据自己的跳转地址截取得到Code
            string code = response.Headers.Location.AbsoluteUri.Replace("http://localhost:5002/signin-oidc?code=", "").Replace("&scope=api1", "");

            #endregion

            #region 获取Token

            var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
            {
                Address = disco.TokenEndpoint,
                ClientId = "apiClientCode",
                ClientSecret = "secret345",
                Code = code,
                GrantType = "authorization_code",
                RedirectUri = "http://localhost:5002/signin-oidc",
                CodeVerifier = codeVerifier
            });


            if (tokenResponse.IsError)
            {
                Console.WriteLine(tokenResponse.Error);
                return null;
            }

            Console.WriteLine(tokenResponse.Json);
            Console.WriteLine(tokenResponse.AccessToken);
            Console.WriteLine("\n\n");

            return tokenResponse.AccessToken;

            #endregion

    
        }


        private  string GenerateCodeVerifier()
        {
            var rng = RandomNumberGenerator.Create();

            var bytes = new byte[32];
            rng.GetBytes(bytes);

            // It is recommended to use a URL-safe string as code_verifier.
            // See section 4 of RFC 7636 for more details.
            var code_verifier = Convert.ToBase64String(bytes)
                .TrimEnd('=')
                .Replace('+', '-')
                .Replace('/', '_');

            return code_verifier;
        }

        private string GenerateCodeChallenge(string code_verifier)
        {
            var code_challenge = string.Empty;
            using (var sha256 = SHA256.Create())
            {
                var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(code_verifier));
                code_challenge = Convert.ToBase64String(challengeBytes)
                    .TrimEnd('=')
                    .Replace('+', '-')
                    .Replace('/', '_');

                return code_challenge;
            }
        }
    }
}

拿到token后,访问API

为了方便测试,直接在Program.cs进行编写访问API的代码了,环境是.net 6,授权中心和API是使用IdentityServer4的官方案例,点击访问

// See https://aka.ms/new-console-template for more information


using ClientAuthorizationCode;
using IdentityModel.Client;
using Newtonsoft.Json.Linq;



AuthorizationCodeLogin authorizationCodeLogin=new AuthorizationCodeLogin();

var token= authorizationCodeLogin.GetTokenAsync().GetAwaiter().GetResult();

var apiClient = new HttpClient();
apiClient.SetBearerToken(token);

var response3 = await apiClient.GetAsync("http://localhost:6011/identity");
if (!response3.IsSuccessStatusCode)
{
    Console.WriteLine(response3.StatusCode);
}
else
{
    var content = await response3.Content.ReadAsStringAsync();
    Console.WriteLine(JArray.Parse(content));
}

Console.ReadLine();


Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐