首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

OAuth 2.0 學習筆記:DotNetOpenAuth 實作 Client Credentials Grant 完整流程

     編輯:關於.NET

Client Credentials Grant 適用於機器對機器 (M2M) 場景:Client 直接以自身身分向 Authorization Server 申請 Access Token,無需使用者介入。本文基於 DotNetOpenAuth 完整示範 Server/Client/Resource Server 三端實作。

流程圖

Client Credentials Flow 1

Client Credentials Flow 2

一、Client 申請 Access Token

public async Task<ActionResult> GetSiteHomePosts()
{
    var clientId = "m.cnblogs.com";
    var clientSecret = "20140213";
    
    // Basic Auth: base64(client_id:client_secret)
    var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{clientId}:{clientSecret}"));
    
    using (var http = new HttpClient())
    {
        http.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Basic", credentials);
        
        var form = new Dictionary<string, string> { { "grant_type", "client_credentials" } };
        var response = await http.PostAsync("https://authserver.example.com/oauth/token", 
            new FormUrlEncodedContent(form));
        
        var json = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            var token = JObject.Parse(json)["access_token"].ToString();
            return Content($"AccessToken: {token}");
        }
        return Content(json);
    }
}

二、Authorization Server 驗證與核發

Client 實體實作 IClientDescription

public class Client : IClientDescription
{
    public string Id { get; set; }
    public string Secret { get; set; }
    
    public Uri DefaultCallback => throw new NotImplementedException();
    public ClientType ClientType { get; set; }
    public bool HasNonEmptySecret => !string.IsNullOrEmpty(Secret);
    public bool IsCallbackAllowed(Uri callback) => throw new NotImplementedException();
    
    // 核心驗證邏輯
    public bool IsValidClientSecret(string secret) => Secret == secret;
}

AuthorizationServerHost 實作

public class AuthorizationServerHost : IAuthorizationServerHost
{
    public static readonly ICryptoKeyStore CryptoKeyStore = new HardCodedKeyCryptoKeyStore("base64-key==");
    
    public IClientDescription GetClient(string clientIdentifier)
    {
        // 從資料庫/快取查詢 Client
        return ServiceLocator.GetService<IClientService>().GetClient(clientIdentifier);
    }
    
    public AccessTokenResult CreateAccessToken(IAccessTokenRequest request)
    {
        var token = new AuthorizationServerAccessToken
        {
            Lifetime = TimeSpan.FromHours(10),
            SymmetricKeyStore = CryptoKeyStore
        };
        return new AccessTokenResult(token);
    }
    
    // Client Credentials Grant 特定驗證
    public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest request)
    {
        var client = GetClient(request.ClientIdentifier);
        if (client == null) 
            return new AutomatedAuthorizationCheckResponse(false, "Client not found");
        
        if (!client.IsValidClientSecret(request.ClientSecret))
            return new AutomatedAuthorizationCheckResponse(false, "Invalid secret");
        
        return new AutomatedAuthorizationCheckResponse(true);
    }
    
    // 其他介面成員略...
    public INonceStore NonceStore => null;
    public bool IsAuthorizationValid(IAuthorizationDescription auth) => true;
}

三、Client 攜帶 Token 呼叫 Resource Server

public async Task<ActionResult> CallApi(string accessToken)
{
    using (var http = new HttpClient())
    {
        http.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", accessToken);
        
        var response = await http.GetAsync("https://api.example.com/blog/posts/sitehome");
        return Content(await response.Content.ReadAsStringAsync());
    }
}

四、Resource Server 驗證 Bearer Token

MessageHandler 統一攔截

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new BearerTokenHandler());
    }
}

public class BearerTokenHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        if (request.Headers.Authorization?.Scheme == "Bearer")
        {
            var analyzer = new StandardAccessTokenAnalyzer(AuthorizationServerHost.CryptoKeyStore);
            var rs = new ResourceServer(analyzer);
            var principal = await rs.GetPrincipalAsync(request, ct);
            
            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;
        }
        return await base.SendAsync(request, ct);
    }
}

Web API Controller 範例

[RoutePrefix("blog/posts")]
public class PostsController : ApiController
{
    [Route("sitehome")]
    public IEnumerable<string> GetSiteHome()
    {
        // User.Identity.Name 來自驗證後的 Principal
        return new[] { $"Client: {User.Identity.Name}" };
    }
}

五、執行結果驗證

呼叫 `/blog/posts/sitehome` 回傳:

["Client: m.cnblogs.com"]

心得總結

  • Client Credentials Grant 流程最簡單,適合後台服務、定時任務、微服務間通訊
  • DotNetOpenAuth 介面設計遵循 RFC 6749,核心在 `IAuthorizationServerHost` 三個 `CheckAuthorize*` 方法
  • 金鑰管理:生產環境請使用 `X509AsymmetricKeyStore` (RSA 非對稱) 替代 `HardCodedKeyCryptoKeyStore`
  • Token 格式:預設為 JWT 相容結構,可自訂 `AuthorizationServerAccessToken` 序列化

實作過程中最大坑點:`CheckAuthorizeClientCredentialsGrant` 回傳型別為 `AutomatedAuthorizationCheckResponse`,非單純 bool,需正確建構回傳物件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved