ASP.Net Core Web Api 使用 IdentityServer4 最新版 踩坑記錄

輔助工具

日誌追蹤包 : Serilog.AspNetCore

源碼查看工具 : ILSpy

項目環境 ###:

ASP.NetCore 3.1

IdentityServer4 4.0.0+

主題內容

測試登錄方式 : password

錯誤內容:

connect/token 登陸出錯

但百度/google網上的示例沒有找到正確的調用方式,無奈只能自己動手,豐衣足食...

首先,先按照之前版本進行傳參

  • POST請求
  • url: connect/token
  • 參數傳遞通過 form-data

調用結果:

<調用結果>
HTTP : 400

{
    "error": "invalid_request"
}

查找調用日誌

Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token

可以看到地址匹配是成功的,那就是校驗不通過了,接着看一下這個類的源碼,找到錯誤觸發地

public async Task<IEndpointResult> ProcessAsync(HttpContext context)
{
	if (!HttpMethods.IsPost(context.Request.Method) || !context.Request.HasApplicationFormContentType())
	{
		return Error("invalid_request");
	}
}

此處有兩個驗證:

  1. POST請求 √
  2. HasApplicationFormContentType ?

查看方法定義:

internal static bool HasApplicationFormContentType(this HttpRequest request)
{
	if (request.ContentType == null)
	{
		return false;
	}
	if (MediaTypeHeaderValue.TryParse(request.ContentType, out MediaTypeHeaderValue parsedValue))
	{
		return parsedValue.MediaType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase);
	}
	return false;
}

... 好樣的,最新版改用了application/x-www-form-urlencoded傳參

然後改一下傳參方式接着調用:

<調用結果>
HTTP : 400

{
    "error": "invalid_scope"
}

換了個錯誤,至少說明傳參改動還是有效的...

查找操作日誌:

IdentityServer4.Validation.TokenRequestValidator
No scopes found in request

key code:

string text = parameters.Get("scope");

if (text.IsMissing())
{
	text = clientAllowedScopes.Distinct().ToSpaceSeparatedString();
}
List<string> requestedScopes = text.ParseScopesString();
if (requestedScopes == null)
{
	LogError("No scopes found in request");
	return false;
}

scope取值:

如果沒有傳值,就去client中取

public static List<string> ParseScopesString(this string scopes)
{
	if (scopes.IsMissing())
	{
		return null;
	}
	scopes = scopes.Trim();
	List<string> list = scopes.Split(new char[1]
	{
		' '
	}, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList();
	if (list.Any())
	{
		list.Sort();
		return list;
	}
	return null;
}

如果有傳值 就用傳值的獲取,多個scope用' '分隔

在配置中,是有AllowedScopes配置,但取不出來? 先不管,用傳值方式先試試...

<調用結果>
HTTP : 400

{
    "error": "invalid_scope"
}

??? 再查下日誌:

IdentityServer4.Validation.DefaultResourceValidator

Scope api1 not found in store.

還好錯誤變了,不然沒得玩了...

先確認配置信息:

return new List<ApiResource>
{
    new ApiResource("api1", "My API")
};

是有這個api1的,好吧,只能再去查看源碼了

DefaultResourceValidator Scope驗證分析

IdentityResource identity = resourcesFromStore.FindIdentityResourcesByScope(requestedScope.ParsedName);
if (identity != null)
{
	if (await IsClientAllowedIdentityResourceAsync(client, identity))
	{
		result.ParsedScopes.Add(requestedScope);
		result.Resources.IdentityResources.Add(identity);
	}
	else
	{
		result.InvalidScopes.Add(requestedScope.RawValue);
	}
	return;
}

首先先通過name去拿IdentityResource,拿到了IdentityResource驗證結果即爲最終結果

ApiScope apiScope = resourcesFromStore.FindApiScope(requestedScope.ParsedName);

沒有拿到IdentityResource則再通過name去拿ApiScope,驗證結果即爲最終結果

IdentityResourceName值配置到Client中的AllowedScopes去,然後再在傳參裏使用IdentityResourceName

<調用結果>
{
    "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjUyM0EzMjE0Q0M4MzAzQjJBRDA2Mzk5N0E2RDI1NDEyIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTQxMTAwNjcsImV4cCI6MTU5NDExMzY2NywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiY2xpZW50X2lkIjoicm8uY2xpZW50Iiwic3ViIjoiMiIsImF1dGhfdGltZSI6MTU5NDExMDA2NywiaWRwIjoibG9jYWwiLCJqdGkiOiJCMEY1NEI2NDI1QzUwRDU2REVEMjBGQUY0QkMwNTE5MiIsImlhdCI6MTU5NDExMDA2Nywic2NvcGUiOlsib3BlbmlkIl0sImFtciI6WyJwd2QiXX0.PqYMwqHfZ3CHtE8q_eCi5H1FVCPKe01uSPiSTNjV0q1m61s98OQezo9M3FCc4bGTw4c6VruylSQAbtT6sid2nYXV05Eq_fD_4KKPTya6NLuTdwgdUohzNN10f3SC0ea1nDhv_94Ewkov_9OWrCSLxAX9yVFKDDs6dB3V53_49n4-3Hd9BkCOevWk-_FzpkMOOhYMi-5LNeZRAXH3G5_GZ7INtypCUx2f0_v84UzQxx2LjcovzAy0ZR3GgFvAh5rgRwd5oBVeiLZOt2ZjvV0b5NAtPSbiEcufFK5box6qm_q2M6GrrMBUm0aTTTd3Vu6Zx-pjjITHQN934EICRKWYFg",
    "expires_in": 3600,
    "token_type": "Bearer",
    "scope": "openid"
}

over... 總算通了


總結

  1. 傳參需要使用 application/x-www-form-urlencoded
  2. 配置ClientAllowedScopes值時要使用IdentityResourceName值,或者配置ApiScope
  3. 傳參傳scope時,通過制指定scope進行驗證,未傳時通過配置的信息(AllowedScopes)進行驗證

新版的ApiResource作用

待補充...

配置參考

public static class Config
{
    public static List<TestUser> GetUsers()
    {
        return new List<TestUser>
        {
            new TestUser
            {
                SubjectId = "1",
                Username = "alice",
                Password = "password"
            },
            new TestUser
            {
                SubjectId = "2",
                Username = "bob",
                Password = "password"
            }
        };
    }

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]
        {
            new IdentityResources.OpenId()
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "client",

                // no interactive user, use the clientid/secret for authentication
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                // secret for authentication
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                // scopes that client has access to
                AllowedScopes = { "api1" }
            },
            // resource owner password grant client
            new Client
            {
                ClientId = "ro.client",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = { "openid" }
            }
        };
    }
}

public void ConfigureServices(IServiceCollection services)
{

    var builder = services.AddIdentityServer()
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryClients(Config.GetClients())
        .AddTestUsers(Config.GetUsers());

    builder.AddDeveloperSigningCredential();

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseIdentityServer();

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章