Error 401 when accessing an API protected by IdentityServer3

六月ゝ 毕业季﹏ 提交于 2019-12-12 04:34:34

问题


I'm getting a 401 error when I try to access a resource from an API protected by IdentityServer3.

I can log in and get the access_token quietly from the Host application of IdentityServer3, but I cannot use the access_token to consume this resource.

I configured my Host of IdentityServer in Startup class like this:

public void Configuration(IAppBuilder app)
{
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Trace()
        .CreateLogger();

    AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
    JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

    // Configure IdentityServer3
    app.Map("/identity", configuration =>
    {
        configuration.UseIdentityServer(new IdentityServerOptions
        {
            SiteName = "IdentityServer3 Sample",
            SigningCertificate = LoadCertificate(),
            Factory = ServiceFactory.Create(),
            RequireSsl = true,

            CspOptions = new CspOptions
            {
                Enabled = true,
                FontSrc = "fonts.googleapis.com"
            },

            AuthenticationOptions = new AuthenticationOptions
            {
                EnablePostSignOutAutoRedirect = true,
            }
        });
    });
}

In my ServiceFactory class, I have:

public static IdentityServerServiceFactory Create()
{
    var factory = new IdentityServerServiceFactory
    {
        ScopeStore = new Registration<IScopeStore>(
            new InMemoryScopeStore(Scopes.GetScopes())),
        ClientStore = new Registration<IClientStore>(
            new InMemoryClientStore(Clients.GetClients())),
        CorsPolicyService = new Registration<ICorsPolicyService>(
            new DefaultCorsPolicyService {AllowAll = true})
    };

    //factory.UseInMemoryUsers(Users.GetUsers());

    ConfigureServices(factory);

    return factory;
}

private static void ConfigureServices(IdentityServerServiceFactory factory)
{
    factory.UserService = new Registration<IUserService, UserService>();

    factory.Register(new Registration<BaseContext>(resolver => new BaseContext()));

    factory.Register(new Registration<AppUserManager>(resolver => new AppUserManager(
        new UserStore<User>(resolver.Resolve<BaseContext>()))));
}

The Scopes:

return new List<Scope>
{
    StandardScopes.OpenId,
    StandardScopes.Profile,
    StandardScopes.OfflineAccess,

    new Scope
    {
        Enabled = true,
        Name = "roles",
        Type = ScopeType.Identity,
        IncludeAllClaimsForUser = true,
        Claims = new List<ScopeClaim>
        {
            new ScopeClaim("role")
        }
    },

    new Scope
    {                    
        Enabled = true,
        Name = "ro",
        Type = ScopeType.Resource,
        IncludeAllClaimsForUser = true,
        Claims = new List<ScopeClaim>
        {
            new ScopeClaim("role")
        }
    }
};

And Clients:

return new List<Client>
{
    new Client
    {
        Enabled = true,
        ClientName = "Hibrid Flow Client",
        ClientId = AppIdentityConstants.ClientIdForHibridFlow,
        Flow = Flows.Hybrid,

        RequireConsent = false,
        AccessTokenType = AccessTokenType.Reference,
        UpdateAccessTokenClaimsOnRefresh = true,

        ClientSecrets = new List<Secret>
        {
            new Secret(AppIdentityConstants.ClientSecret.Sha256())
        },
        AllowedScopes = new List<string>
        {
            Constants.StandardScopes.OpenId,
            Constants.StandardScopes.Profile,
            Constants.StandardScopes.Email,
            Constants.StandardScopes.Roles,
            Constants.StandardScopes.OfflineAccess,
        },
        RedirectUris = new List<string>
        {
            AppIdentityConstants.IdentityAddress,
            AppIdentityConstants.CRMAddress
        },
        PostLogoutRedirectUris = new List<string>
        {
            AppIdentityConstants.IdentityAddress,
            AppIdentityConstants.CRMAddress
        },
        LogoutSessionRequired = true
    },

    new Client
    {
        Enabled = true,
        ClientName = "Resource Owner Client",
        ClientId = AppIdentityConstants.ClientIdForResourceOwnerFlow,
        Flow = Flows.ResourceOwner,

        RequireConsent = false,
        AccessTokenType = AccessTokenType.Jwt,
        UpdateAccessTokenClaimsOnRefresh = true,
        AccessTokenLifetime = 3600,

        ClientSecrets = new List<Secret>
        {
            new Secret(AppIdentityConstants.ClientSecret.Sha256())
        },
        AllowedScopes = new List<string>
        {
            Constants.StandardScopes.OpenId,
            Constants.StandardScopes.Profile,
            Constants.StandardScopes.Email,
            Constants.StandardScopes.Roles,
            Constants.StandardScopes.OfflineAccess,
            "ro"
        },
        AllowAccessTokensViaBrowser = true,
        AbsoluteRefreshTokenLifetime = 86400,
        SlidingRefreshTokenLifetime = 43200,
        RefreshTokenUsage = TokenUsage.OneTimeOnly,
        RefreshTokenExpiration = TokenExpiration.Sliding
    },
};

This is the source code for the Host Application of IdentityServer3.

And now I'll show you how I've set up my API. This is my Startup class:

public void Configuration(IAppBuilder app)
{
    JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();

    app.UseIdentityServerBearerTokenAuthentication(
        new IdentityServerBearerTokenAuthenticationOptions
    {
        Authority = AppIdentityConstants.IdentityBaseAddress,
        RequiredScopes = new[] { "ro", "offline_access" },
        ClientId = AppIdentityConstants.ClientIdForResourceOwnerFlow,
        ClientSecret = AppIdentityConstants.ClientSecret,
    });
}

The AppIdentityConstants.IdentityBaseAddress is https://localhost:44342/identity.

And, in Global.asax.cs I call these configurations:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        var formatters = GlobalConfiguration.Configuration.Formatters;
        var jsonFormatter = formatters.JsonFormatter;
        var settings = jsonFormatter.SerializerSettings;

        #if DEBUG
        settings.Formatting = Formatting.Indented;
        #endif

        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

And AuthorizeAttribute filter:

public class FilterConfig
{
    public static void RegisterGlobalFilters(HttpConfiguration configuration)
    {
        configuration.Filters.Add(new AuthorizeAttribute());
    }
}

To test I did the following:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <script>
        function done(response) { console.log(response); }
        function always(response) { console.log("always"); }
        function fail(response) { console.log("fail"); }
        var custom = {
            client_id: "ro",
            client_secret: "client_secret",
            scope: "ro offline_access",
        };

        $(function () {
            var settings = {
                "async": true,
                "crossDomain": true,
                "url": "https://localhost:44342/identity/connect/token",
                "method": "POST",
                "headers": {
                    "content-type": "application/x-www-form-urlencoded",
                    "cache-control": "no-cache"
                },
                "data": {
                    "client_id": custom.client_id,
                    "client_secret": custom.client_secret,
                    "scope": custom.scope,
                    "username": "user@test.com",
                    "password": "123456",
                    "grant_type": "password"
                }
            }
            $.ajax(settings).done(function (response){
                done(response);
                checkStatus(response.access_token);
            }).always(always).fail(fail);

            function checkStatus(access_token) {
                var settings2 = {
                    "async": true,
                    "crossDomain": true,
                    "url": "https://localhost:44352/api/importer/status",
                    "method": "GET",
                    xhrFields: {
                        withCredentials: true
                    },
                    "headers": {
                        "Authorization": "Bearer " + access_token,
                        "cache-control": "no-cache"
                    }
                }
                $.ajax(settings2).done(done).always(always).fail(fail);
            }
        });
    </script>
</body>
</html>

The first request, which is to obtain the access data, including the acess_token, is done successfully.

But the second request, which is made to the API, returns a 401 error.
And as I showed earlier, the API is protected with the AuthorizeAttribute.

What is wrong?


回答1:


If you debug the checkStatus function, does the acessData parameter have an access_token property?

If so, then did you install the Microsoft.Owin.Host.SystemWeb NuGet package in your Web API project? What could happen is that your OWIN pipeline is not executed because you're missing that package. So the access token is not transformed into an identity, and the request stays unauthenticated, which could explain the HTTP 401 response.



来源:https://stackoverflow.com/questions/41169273/error-401-when-accessing-an-api-protected-by-identityserver3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!