C#中是否有JSON Web Token(JWT)的示例?

116

我感觉我有点像疯了。通常在网上为了完成任何任务都会有无数的库和示例可供使用。我正在尝试使用JSON Web Tokens(JWT)通过Google“服务账户”实现身份验证,详情请参见此处

然而,只有PHP、Python和Java的客户端库可用。即使在谷歌身份验证之外搜索JWT示例,也只能得到很少的信息。这是真的很新颖还是可能是谷歌的专有系统吗?

Java示例是我能够理解的最接近的内容,看起来非常复杂和令人生畏。肯定有一些C#的东西可以让我至少开始。任何帮助都将不胜感激!


2
Peter有你的答案。JWT是一种相对较新的令牌格式,这就是为什么样本仍然有点难以获得,但它正在迅速增长,因为JWT是SWT的必要替代品。微软支持该令牌格式,例如Live Connect API使用JWT。 - Andrew Lavers
这与App Engine有关系吗? - Nick Johnson
可能是验证Google OpenID Connect JWT ID令牌的重复问题。 - Thomas
7个回答

81
我发现了一个Json Web Token的基本实现,并在此基础上添加了Google的风格。虽然我还没有完全解决它,但已经完成了97%。这个项目失去了动力,所以希望这会帮助其他人有一个很好的起点:
注意:我对基本实现所做的更改(无法记起我在哪里找到它)是:
1. 将HS256更改为RS256 2. 交换头部中JWT和alg的顺序。不确定是Google还是规范搞错了,但根据他们的文档,Google接受下面的方式。
public enum JwtHashAlgorithm
{
    RS256,
    HS384,
    HS512
}
    
public class JsonWebToken
{
    private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

    static JsonWebToken()
    {
        HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
            {
                { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
            };
    }

    public static string Encode(object payload, string key, JwtHashAlgorithm algorithm)
    {
        return Encode(payload, Encoding.UTF8.GetBytes(key), algorithm);
    }

    public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm)
    {
        var segments = new List<string>();
        var header = new { alg = algorithm.ToString(), typ = "JWT" };

        byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
        byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
        //byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}");

        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));

        var stringToSign = string.Join(".", segments.ToArray());

        var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);

        byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign);
        segments.Add(Base64UrlEncode(signature));

        return string.Join(".", segments.ToArray());
    }

    public static string Decode(string token, string key)
    {
        return Decode(token, key, true);
    }

    public static string Decode(string token, string key, bool verify)
    {
        var parts = token.Split('.');
        var header = parts[0];
        var payload = parts[1];
        byte[] crypto = Base64UrlDecode(parts[2]);

        var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        var headerData = JObject.Parse(headerJson);
        var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        var payloadData = JObject.Parse(payloadJson);

        if (verify)
        {
            var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var algorithm = (string)headerData["alg"];

            var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
            var decodedCrypto = Convert.ToBase64String(crypto);
            var decodedSignature = Convert.ToBase64String(signature);

            if (decodedCrypto != decodedSignature)
            {
                throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
            }
        }

        return payloadData.ToString();
    }

    private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
    {
        switch (algorithm)
        {
            case "RS256": return JwtHashAlgorithm.RS256;
            case "HS384": return JwtHashAlgorithm.HS384;
            case "HS512": return JwtHashAlgorithm.HS512;
            default: throw new InvalidOperationException("Algorithm not supported.");
        }
    }

    // from JWT spec
    private static string Base64UrlEncode(byte[] input)
    {
        var output = Convert.ToBase64String(input);
        output = output.Split('=')[0]; // Remove any trailing '='s
        output = output.Replace('+', '-'); // 62nd char of encoding
        output = output.Replace('/', '_'); // 63rd char of encoding
        return output;
    }

    // from JWT spec
    private static byte[] Base64UrlDecode(string input)
    {
        var output = input;
        output = output.Replace('-', '+'); // 62nd char of encoding
        output = output.Replace('_', '/'); // 63rd char of encoding
        switch (output.Length % 4) // Pad with trailing '='s
        {
            case 0: break; // No pad chars in this case
            case 2: output += "=="; break; // Two pad chars
            case 3: output += "="; break; // One pad char
            default: throw new System.Exception("Illegal base64url string!");
        }
        var converted = Convert.FromBase64String(output); // Standard base64 decoder
        return converted;
    }
}

然后是我的Google特定的JWT类:

public class GoogleJsonWebToken
{
    public static string Encode(string email, string certificateFilePath)
    {
        var utc0 = new DateTime(1970,1,1,0,0,0,0, DateTimeKind.Utc);
        var issueTime = DateTime.Now;

        var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
        var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds; // Expiration time is up to 1 hour, but lets play on safe side

        var payload = new
        {
            iss = email,
            scope = "https://www.googleapis.com/auth/gan.readonly",
            aud = "https://accounts.google.com/o/oauth2/token",
            exp = exp,
            iat = iat
        };

        var certificate = new X509Certificate2(certificateFilePath, "notasecret");

        var privateKey = certificate.Export(X509ContentType.Cert);

        return JsonWebToken.Encode(payload, privateKey, JwtHashAlgorithm.RS256);
    }
}

9
实现似乎来自John Sheehan的JWT库:https://github.com/johnsheehan/jwt - Torbjørn
18
此版本不正确支持RS256签名算法!它只是将输入哈希为以密钥字节作为密钥的方式,而不是应该在PKI中进行的正确加密哈希。它仅仅将HS256标签替换为RS256标签,但没有正确实现。 - Hans Z.
2
以上代码在一定程度上容易受到以下安全攻击: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ 如果服务器期望接收使用RSA签名的令牌,但实际上接收到的是使用HMAC签名的令牌,则服务器会将公钥误认为是HMAC密钥。 - BennyBechDk
1
@Levitikon 你有什么想法可以解码谷歌在JSON文件中提供的private_key吗?谢谢。 - bibscy
代码示例对我不起作用,使用RS256算法签名验证失败。 - Karthick Jayaraman
显示剩余5条评论

21

6
这是我在.NET中实现的(谷歌)JWT验证。它基于Stack Overflow和GitHub Gist上的其他实现。
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace QuapiNet.Service
{
    public class JwtTokenValidation
    {
        public async Task<Dictionary<string, X509Certificate2>> FetchGoogleCertificates()
        {
            using (var http = new HttpClient())
            {
                var response = await http.GetAsync("https://www.googleapis.com/oauth2/v1/certs");

                var dictionary = await response.Content.ReadAsAsync<Dictionary<string, string>>();
                return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value)));
            }
        }

        private string CLIENT_ID = "xxx.apps.googleusercontent.com";

        public async Task<ClaimsPrincipal> ValidateToken(string idToken)
        {
            var certificates = await this.FetchGoogleCertificates();

            TokenValidationParameters tvp = new TokenValidationParameters()
            {
                ValidateActor = false, // check the profile ID

                ValidateAudience = true, // check the client ID
                ValidAudience = CLIENT_ID,

                ValidateIssuer = true, // check token came from Google
                ValidIssuers = new List<string> { "accounts.google.com", "https://accounts.google.com" },

                ValidateIssuerSigningKey = true,
                RequireSignedTokens = true,
                IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
                IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
                {
                    return certificates
                    .Where(x => x.Key.ToUpper() == kid.ToUpper())
                    .Select(x => new X509SecurityKey(x.Value));
                },
                ValidateLifetime = true,
                RequireExpirationTime = true,
                ClockSkew = TimeSpan.FromHours(13)
            };

            JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
            SecurityToken validatedToken;
            ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);

            return cp;
        }
    }
}

请注意,在使用它之前,您需要添加一个对NuGet包System.Net.Http.Formatting.Extension的引用。没有它,编译器将无法识别ReadAsAsync<>方法。


如果提供了IssuerSigningKeyResolver,为什么需要设置IssuerSigningKeys呢? - AsifM
@AsifMD 真的不知道,而且现在也无法测试。也许可以在不设置IssuerSigningKey的情况下工作。您还需要更改解析器代码以请求证书,否则在几天后谷歌更改其证书时会出现错误。 - Thomas
使用PM> Install-Package System.IdentityModel.Tokens.Jwt -Version 5.2.4来支持System.IdentityModel,这是最简单的方法。 - Karthick Jayaraman

3
最好使用标准和著名的库,而不是从头开始编写代码。
以下是一些库的链接,可以使用它们进行编码、解码和加密:
1. JWT JWT token编码和解码 2. Bouncy Castle 支持加密和解密,特别是RS256 下载地址在此 使用这些库,您可以生成JWT令牌,并进行RS256签名,如下所示。
public string GenerateJWTToken(string rsaPrivateKey)
{
    var rsaParams = GetRsaParameters(rsaPrivateKey);
    var encoder = GetRS256JWTEncoder(rsaParams);

    // create the payload according to the Google's doc
    var payload = new Dictionary<string, object>
    {
        { "iss", ""},
        { "sub", "" },
        // and other key-values according to the doc
    };

    // add headers. 'alg' and 'typ' key-values are added automatically.
    var header = new Dictionary<string, object>
    {
        { "kid", "{your_private_key_id}" },
    };

    var token = encoder.Encode(header,payload, new byte[0]);

    return token;
}

private static IJwtEncoder GetRS256JWTEncoder(RSAParameters rsaParams)
{
    var csp = new RSACryptoServiceProvider();
    csp.ImportParameters(rsaParams);

    var algorithm = new RS256Algorithm(csp, csp);
    var serializer = new JsonNetSerializer();
    var urlEncoder = new JwtBase64UrlEncoder();
    var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

    return encoder;
}

private static RSAParameters GetRsaParameters(string rsaPrivateKey)
{
    var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
    using (var ms = new MemoryStream(byteArray))
    {
        using (var sr = new StreamReader(ms))
        {
            // use Bouncy Castle to convert the private key to RSA parameters
            var pemReader = new PemReader(sr);
            var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
            return DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
        }
    }
}

注:RSA私钥应具有以下格式:

-----BEGIN RSA PRIVATE KEY-----
 {base64 formatted value}
-----END RSA PRIVATE KEY-----

0

我使用了System.Security.Cryptography.RSA,并对John Sheehan的JWT库代码进行了修改,该代码是由@Levitikon扩展而来,以使用RS256带有SHA-256哈希算法的RSASSA-PKCS1-v1_5

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// adapted from<br/>
/// https://github.com/jwt-dotnet/jwt<br/>
/// https://dev59.com/6Gkw5IYBdhLWcg3wNX33
/// https://dev59.com/6Gkw5IYBdhLWcg3wNX33#10106800<br/>
/// <br/>
/// JSON Web Token (JWT) is a compact, URL-safe means of representing<br/>
/// claims to be transferred between two parties.  The claims in a JWT<br/>
/// are encoded as a JSON object that is used as the payload of a JSON<br/>
/// Web Signature (JWS) structure or as the plaintext of a JSON Web<br/>
/// Encryption(JWE) structure, enabling the claims to be digitally<br/>
/// signed or integrity protected with a Message Authentication Code<br/>
/// (MAC) and/or encrypted.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7519
/// </summary>
internal class JsonWebToken
{
    /// <summary>
    /// JWS uses cryptographic algorithms to digitally sign or create a MAC<br/>
    /// of the contents of the JWS Protected Header and the JWS Payload.<br/>
    /// <br/>
    /// https://www.rfc-editor.org/rfc/rfc7518#section-3
    /// </summary>
    public enum JwsAlgorythm
    {
        /// <summary>
        /// RSASSA-PKCS1-v1_5 using SHA-256<br/>
        /// This section defines the use of the RSASSA-PKCS1-v1_5 digital<br/>
        /// signature algorithm as defined in Section 8.2 of RFC 3447 [RFC3447]<br/>
        /// (commonly known as PKCS #1), using SHA-2 [SHS] hash functions.<br/>
        /// The RSASSA-PKCS1-v1_5 SHA-256 digital signature is generated as<br/>
        /// follows: generate a digital signature of the JWS Signing Input using<br/>
        /// RSASSA-PKCS1-v1_5-SIGN and the SHA-256 hash function with the desired<br/>
        /// private key.  This is the JWS Signature value.<br/>
        /// <br/>
        /// https://www.rfc-editor.org/rfc/rfc7518#section-3.3<br/>
        /// https://www.rfc-editor.org/rfc/rfc3447#section-8.2<br/>
        /// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
        /// </summary>
        RS256,
        /// <summary>
        /// No digital signature or MAC performed<br/>
        /// JWSs MAY also be created that do not provide integrity protection.<br/>
        /// Such a JWS is called an Unsecured JWS.  An Unsecured JWS uses the<br/>
        /// "alg" value "none" and is formatted identically to other JWSs, but<br/>
        /// MUST use the empty octet sequence as its JWS Signature value.<br/>
        /// Recipients MUST verify that the JWS Signature value is the empty<br/>
        /// octet sequence.<br/>
        /// <br/>
        /// https://www.rfc-editor.org/rfc/rfc7518#section-3.6<br/>
        /// https://www.rfc-editor.org/rfc/rfc7519#section-6
        /// </summary>
        none
    }
    public static string Encode(object payload, JwsAlgorythm algo, RSA rsa)
    {
        if (payload == null) { throw new ArgumentNullException("payload"); }
        if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
        if (rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Encoding of secured JWT requires an RSA object"); }
        List<string> segments = new List<string>();
        var header = new { typ = "JWT", alg = algo.ToString() };

        string strHeader = JsonConvert.SerializeObject(header, Formatting.None);
        string strPayload = JsonConvert.SerializeObject(payload, Formatting.None);
        byte[] headerBytes = Encoding.UTF8.GetBytes(strHeader);
        byte[] payloadBytes = Encoding.UTF8.GetBytes(strPayload);

        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));

        if (algo == JwsAlgorythm.none)
        {
            segments.Add(string.Empty);
            return string.Join(".", segments.ToArray());
        }

        string stringToSign = string.Join(".", segments.ToArray());

        byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
        byte[] signature = rsa.SignData(bytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        segments.Add(Base64UrlEncode(signature));
        return string.Join(".", segments.ToArray());
    }
    public static Tuple<string, string> Decode(string token, JwsAlgorythm algo, bool verify, RSA rsa = null)
    {
        if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
        if (verify && rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Verification of secured JWT requires an RSA object"); }
        string[] parts = token.Split('.');
        string header = parts[0];
        string payload = parts[1];
        byte[] crypto = Base64UrlDecode(parts[2]);

        string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        JObject headerData = JObject.Parse(headerJson);
        string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        JObject payloadData = JObject.Parse(payloadJson);

        if (verify)
        {
            if (algo == JwsAlgorythm.none)
            {
                if (crypto.Length != 0)
                {
                    throw new ApplicationException(string.Format("Invalid signature"));
                }
            }
            else if (algo == JwsAlgorythm.RS256)
            {
                byte[] bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
                bool valid = rsa.VerifyData(bytesToSign, crypto, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                if (!valid)
                {
                    throw new ApplicationException(string.Format("Invalid signature"));
                }
            }
        }
        return new Tuple<string, string>(headerData.ToString(), payloadData.ToString());
    }

    // from JWT spec
    private static string Base64UrlEncode(byte[] input)
    {
        string output = Convert.ToBase64String(input);
        output = output.Split('=')[0]; // Remove any trailing '='s
        output = output.Replace('+', '-'); // 62nd char of encoding
        output = output.Replace('/', '_'); // 63rd char of encoding
        return output;
    }

    // from JWT spec
    private static byte[] Base64UrlDecode(string input)
    {
        string output = input;
        output = output.Replace('-', '+'); // 62nd char of encoding
        output = output.Replace('_', '/'); // 63rd char of encoding
        switch (output.Length % 4) // Pad with trailing '='s
        {
            case 0: break; // No pad chars in this case
            case 2: output += "=="; break; // Two pad chars
            case 3: output += "="; break; // One pad char
            default: throw new Exception("Invalid base64url string");
        }
        byte[] converted = Convert.FromBase64String(output); // Standard base64 decoder
        return converted;
    }
}

使用方法:

X509Certificate2 cert = new X509Certificate2("C:\\test\\keypair.pfx", "notasecret");

long secsSinceEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var jwt = new
{
    exp = secsSinceEpoch + 600,
    iss = "my client id",
    aud = "h++ps://webapi.com",
    sub = "my subscriber id",
    iat = secsSinceEpoch,
    nbf = secsSinceEpoch,
    jti = RandomString(21),
};

string jwtEncoded = JsonWebToken.Encode(jwt, JsonWebToken.JwsAlgorythm.RS256, cert.GetRSAPrivateKey());
Tuple<string, string> jwtDecoded = JsonWebToken.Decode(jwtEncoded, JsonWebToken.JwsAlgorythm.RS256, true, cert.GetRSAPublicKey());

Console.WriteLine(jwtDecoded);

输出:

({
  "typ": "JWT",
  "alg": "RS256"
}, {
  "exp": 1668732075,
  "iss": "my client id",
  "aud": "h++ps://webapi.com",
  "sub": "my subscriber id",
  "iat": 1668731475,
  "nbf": 1668731475,
  "jti": "tCUpk2i5bNBVBcj7LzV5U"
})

0

这是类和函数的列表:

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.IdentityModel.Tokens
open System.IdentityModel.Tokens
open System.IdentityModel.Tokens.Jwt
open Microsoft.IdentityModel.JsonWebTokens
open System.Text
open Newtonsoft.Json
open System.Security.Claims
    let theKey = "VerySecretKeyVerySecretKeyVerySecretKey"
    let securityKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(theKey))
    let credentials = SigningCredentials(securityKey, SecurityAlgorithms.RsaSsaPssSha256)
    let expires = DateTime.UtcNow.AddMinutes(123.0) |> Nullable
    let token = JwtSecurityToken(
                    "lahoda-pro-issuer", 
                    "lahoda-pro-audience",
                    claims = null,
                    expires =  expires,
                    signingCredentials = credentials
        )

    let tokenString = JwtSecurityTokenHandler().WriteToken(token)

0

这里是另一个只使用RESTful API的 Google 服务账户访问G Suite 用户和用户组的工作示例,通过JWT进行身份验证。这仅通过反射 Google 库才成为可能,因为 Google 对于这些API的文档资料简直糟糕透了。任何习惯于使用微软技术的人都会在弄清楚 Google 服务中的各个部分如何配合时感到相当困难。

$iss = "<name>@<serviceaccount>.iam.gserviceaccount.com"; # The email address of the service account.
$sub = "impersonate.user@mydomain.com"; # The user to impersonate (required).
$scope = "https://www.googleapis.com/auth/admin.directory.user.readonly https://www.googleapis.com/auth/admin.directory.group.readonly";
$certPath = "D:\temp\mycertificate.p12";
$grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";

# Auxiliary functions
function UrlSafeEncode([String] $Data) {
    return $Data.Replace("=", [String]::Empty).Replace("+", "-").Replace("/", "_");
}

function UrlSafeBase64Encode ([String] $Data) {
    return (UrlSafeEncode -Data ([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Data))));
}

function KeyFromCertificate([System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $privateKeyBlob = $Certificate.PrivateKey.ExportCspBlob($true);
    $key = New-Object System.Security.Cryptography.RSACryptoServiceProvider;
    $key.ImportCspBlob($privateKeyBlob);
    return $key;
}

function CreateSignature ([Byte[]] $Data, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $sha256 = [System.Security.Cryptography.SHA256]::Create();
    $key = (KeyFromCertificate $Certificate);
    $assertionHash = $sha256.ComputeHash($Data);
    $sig = [Convert]::ToBase64String($key.SignHash($assertionHash, "2.16.840.1.101.3.4.2.1"));
    $sha256.Dispose();
    return $sig;
}

function CreateAssertionFromPayload ([String] $Payload, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
    $header = @"
{"alg":"RS256","typ":"JWT"}
"@;
    $assertion = New-Object System.Text.StringBuilder;

    $assertion.Append((UrlSafeBase64Encode $header)).Append(".").Append((UrlSafeBase64Encode $Payload)) | Out-Null;
    $signature = (CreateSignature -Data ([System.Text.Encoding]::ASCII.GetBytes($assertion.ToString())) -Certificate $Certificate);
    $assertion.Append(".").Append((UrlSafeEncode $signature)) | Out-Null;
    return $assertion.ToString();
}

$baseDateTime = New-Object DateTime(1970, 1, 1, 0, 0, 0, [DateTimeKind]::Utc);
$timeInSeconds = [Math]::Truncate([DateTime]::UtcNow.Subtract($baseDateTime).TotalSeconds);

$jwtClaimSet = @"
{"scope":"$scope","email_verified":false,"iss":"$iss","sub":"$sub","aud":"https://oauth2.googleapis.com/token","exp":$($timeInSeconds + 3600),"iat":$timeInSeconds}
"@;


$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "notasecret", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);
$jwt = CreateAssertionFromPayload -Payload $jwtClaimSet -Certificate $cert;


# Retrieve the authorization token.
$authRes = Invoke-WebRequest -Uri "https://oauth2.googleapis.com/token" -Method Post -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -Body @"
assertion=$jwt&grant_type=$([Uri]::EscapeDataString($grantType))
"@;
$authInfo = ConvertFrom-Json -InputObject $authRes.Content;

$resUsers = Invoke-WebRequest -Uri "https://www.googleapis.com/admin/directory/v1/users?domain=<required_domain_name_dont_trust_google_documentation_on_this>" -Method Get -Headers @{
    "Authorization" = "$($authInfo.token_type) $($authInfo.access_token)"
}

$users = ConvertFrom-Json -InputObject $resUsers.Content;

$users.users | ft primaryEmail, isAdmin, suspended;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接