问题
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