輔助工具
日誌追蹤包 : 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");
}
}
此處有兩個驗證:
- POST請求 √
- 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,驗證結果即爲最終結果
把IdentityResource的Name值配置到Client中的AllowedScopes去,然後再在傳參裏使用IdentityResource的Name值
<調用結果>
{
"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... 總算通了
總結
- 傳參需要使用 application/x-www-form-urlencoded
- 配置Client的AllowedScopes值時要使用IdentityResource的Name值,或者配置ApiScope
- 傳參傳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();
}