Identity Server 4 的 API 授权不断返回 401 Unauthorized

Posted

技术标签:

【中文标题】Identity Server 4 的 API 授权不断返回 401 Unauthorized【英文标题】:API Authorization with Identity Server 4 keeps returning 401 Unauthorized 【发布时间】:2020-03-24 13:04:08 【问题描述】:

我正在使用 Identity Server 4 .Net Core 3,如果我在启动时使用标准配置,我的 API 端点不会验证访问令牌,我不断收到 401 Unauthorized,但是当我在控制器中使用授权属性设置身份验证方案时,我可以使用相同的令牌成功访问我的端点...

[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
public class MyWebAPiControllerController : ControllerBase

.......

这是我的身份服务器配置:

//API resource       
public IEnumerable<ApiResource> Apis()

        var resources = new List<ApiResource>();

        resources.Add(new ApiResource("identity", "My API", new[]  JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Role, JwtClaimTypes.Profile ));

        return resources;

我的客户端配置:

public IEnumerable<Client> Clients()
    

        var Clients = new List<Client>();

        Clients.Add(new Client
        
            ClientId = "client",
            ClientSecrets =  new Secret(_securityConfig.Secret.Sha256()) ,

            AllowedGrantTypes = GrantTypes.ClientCredentials,
            // scopes that client has access to
            AllowedScopes =  "identity" 
        );

        Clients.Add(new Client
        
            ClientId = "mvc",
            ClientName = "MVC Client",

            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
            //RequirePkce = true,
            ClientSecrets =  new Secret(_securityConfig.Secret.Sha256()) ,
            RequireConsent = false,
            RedirectUris = _securityConfig.RedirectURIs,
            FrontChannelLogoutUri = _securityConfig.SignoutUris,
            PostLogoutRedirectUris = _securityConfig.PostLogoutUris,
            AllowOfflineAccess = true,
            AllowAccessTokensViaBrowser = true,
            AllowedScopes = new List<string>
                
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "identity"
                

        );

        return Clients;
     

我的 API 配置

 services.AddAuthentication("Bearer")
            .AddJwtBearer("Bearer", options =>
            
                options.Authority = _securityConfig.Authority;
                options.RequireHttpsMetadata = false;

                options.Audience = "identity";
            );

最后是我的网络应用、OIDC 配置以及我如何获取访问令牌:

        services.AddAuthentication(options =>
        
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        ).AddCookie(options =>
            
                options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
                options.Cookie.Name = "identity_cookie";
            )
        .AddOpenIdConnect("oidc", options =>
        
            options.Events = new OpenIdConnectEvents
            
                OnUserInformationReceived = async ctx =>
                
                    //Get Token here and assign to Cookie for use in Jquery
                    ctx.HttpContext.Response.Cookies.Append("bearer_config", ctx.ProtocolMessage.AccessToken);
                
            ;

            options.Authority = _securityConfig.Authority;
            options.RequireHttpsMetadata = false;

            options.ClientId = "mvc";
            options.ClientSecret = _securityConfig.Secret;
            options.ResponseType = "code id_token";
            options.SaveTokens = true;


            options.Scope.Clear();
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
            options.Scope.Add("identity");
            options.Scope.Add("offline_access");

            options.ClaimActions.MapAllExcept("iss", "nbf", "exp", "aud", "nonce", "iat", "c_hash");

            options.GetClaimsFromUserInfoEndpoint = true;
            //options.SaveTokens = true;

            options.TokenValidationParameters = new TokenValidationParameters
            
                NameClaimType = JwtClaimTypes.Name,
                RoleClaimType = JwtClaimTypes.Role,
            ;


        );

关于为什么我不断收到 401 Unauthorized 的任何想法?

【问题讨论】:

你能验证 Startup.Configure 中的order of the middleware 吗?尤其是UseAuthorization 声明。 @RuardvanElburg,我的中间件如下:app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseAuthentication(); 反过来,UseAuthentication 然后 UseAuthorization。首先识别用户,然后授权用户。这就是为什么中间件不起作用,但属性起作用的原因。 @RuardvanElburg 成功了!只需将其添加为答案,我就会接受。先生,您是一位学者,一位绅士! 如果你傻到复制和粘贴 app.UseAuthorization(); 也会发生这种情况。两次,而不是像我的抱歉那样拥有 app.UseAuthentication() 。我花了几个小时才意识到这一点。 【参考方案1】:

根据所描述的行为,我认为这可能与中间件配置有关,更具体地说是中间件的顺序。但我不能确定,因为 Startup.Configure 在问题中不可用。

幸运的是,雅克可以确认问题确实出在订单上。如评论中所述:

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();

这里的问题是用户首先被授权(UseAuthorization)然后被认证(UseAuthentication)。因此,用户永远无法被授权,因为此时它是未知的(匿名的)。但稍后,当属性被验证时,用户是已知的。这就解释了为什么会这样。

为了解决这个问题,必须切换语句。先认证用户(识别用户,用户是谁?)再授权用户:

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

订单在documentation中描述。

【讨论】:

以上是关于Identity Server 4 的 API 授权不断返回 401 Unauthorized的主要内容,如果未能解决你的问题,请参考以下文章

Identity Server 4 - Hybrid Flow - 保护API资源

.Net Core 自定义身份验证使用 API 密钥和 Identity Server 4

Identity Server 4 无法验证来自 localhost 的受保护 API 的令牌

无法让 Identity Server 4 API 授权其自己的端点,而是发送登录页面

如何使用 Identity Server 4 (JWT) 进行基于角色的 Web API 授权

Identity Server 4 原理和实战(完结)_单点登录实例(添加Flask客户端,Express.js的API)