问题
I am currently trying to use 2 different bearer tokens in a .net core 2.2 app. I would like to use an Identity Server token and an Azure AD bearer token. According to Microsoft this is possible (https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-2.2) but I am having no success getting it working.
I have the Identity Server token as the "default" authentication followed by the AzureAD token as documented in the aforementioned link:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ClockSkew = ClockSkew
};
o.Audience = Audience;
o.Authority = IdentityIssuer;
o.RequireHttpsMetadata = true;
})
.AddJwtBearer("AzureAd",o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
};
o.Audience = AudienceUri;
o.Authority = Authority
});
Identity Server tokens validate as expected; however Azure AD tokens do not. They appear to always hit the default Bearer token handler.
回答1:
Try with something like this (I have 2 auth schemes; one for AAD and another one for custom Bearer auth)
var url = new MongoUrl(mongoSettings.ConnectionString); // I'm using MONGODB as databse ..but you can choose what you want
var client = new MongoClient(url);
var database = client.GetDatabase(url.DatabaseName);
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 0;
// ApplicationUser settings
options.User.RequireUniqueEmail = false;
//options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@.-_";
}).RegisterMongoStores<ApplicationUser, ApplicationRole>(
p => database.GetCollection<ApplicationUser>("AspNetUsers"),
p => database.GetCollection<ApplicationRole>("AspNetRoles"))
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appConfiguration.Key));
var tokenValidationParameters = new TokenValidationParameters
{
//RequireExpirationTime = true,
//RequireSignedTokens = true,
//ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = false,
ValidIssuer = appConfiguration.SiteUrl,
ValidateAudience = false,
ValidAudience = appConfiguration.SiteUrl,
//ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
//options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer("AAD", options =>
{
//options.Audience = appConfiguration.SiteUrl;
//options.ClaimsIssuer = appConfiguration.SiteUrl;
options.IncludeErrorDetails = true;
options.Authority = "https://sts.windows.net/800859e2-e8c3-4842-b31a-3b3727070cb6/v2.0";
options.Audience = "5e2ddaf2-2ed3-4829-bbe8-9aa127a754ef";
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if ((context.Request.Path.Value.StartsWith("/videohub")
//|| context.Request.Path.Value.StartsWith("/looney")
//|| context.Request.Path.Value.StartsWith("/usersdm")
)
&& context.Request.Query.TryGetValue("token", out StringValues token)
)
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
//TODO:
return Task.FromResult(0);
},
OnTokenValidated = context =>
{
//At this point, the security token has been validated successfully and a ClaimsIdentity has been created
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//get username
var preferred_username = claimsIdentity.Claims.ToList().Where(c => c.Type == "preferred_username").Select(c => c.Value).FirstOrDefault();
var username = !string.IsNullOrEmpty(preferred_username) ? preferred_username : claimsIdentity.Claims.ToList().Where(c => c.Type == "upn").Select(c => c.Value).FirstOrDefault();
//add your custom claims here
var serviceProvider = services.BuildServiceProvider();
var userservice = serviceProvider.GetService<IUsersService>();
var us = userservice.Find(xx => xx.UserName == username);
if (us == null) return Task.FromResult(0);
// ADD SCHEMA (so we know which kind of token is .. from AZURE ACTIVE DIRECTORY .. OR CUSTOM)
// TO RETRIEVE THE SCHEMA ..--> //var result = User.Claims.Where(c=>c.Type=="schema").FirstOrDefault().Value;
claimsIdentity.AddClaim(new Claim("schema", "AAD"));
//GET ROLES FROM DB
if (us != null && us.Roles.Any())
{
//add THEM
us.Roles.ForEach(rr =>
{
claimsIdentity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, rr.ToUpper()));
});
}
else
{
//OR ADD A DEFAULT ONE
claimsIdentity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, Constant.ROLES.Dipendente));
}
// add MONGDB Id as ClaimTypes.NameIdentifier
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, us.Id));
return Task.FromResult(0);
}
};
}).AddJwtBearer("CUSTOM", options =>
{
//options.Audience = appConfiguration.SiteUrl;
//options.ClaimsIssuer = appConfiguration.SiteUrl;
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
//TODO:
return Task.FromResult(0);
},
OnTokenValidated = context =>
{
//At this point, the security token has been validated successfully and a ClaimsIdentity has been created
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
// ADD SCHEMA (so we know which kind of token is .. from AZURE ACTIVE DIRECOTY .. OR CUSTOM)
claimsIdentity.AddClaim(new Claim("schema", "CUSTOM"));
return Task.FromResult(0);
}
};
});
then in yours Controller mark class or methid as :
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = "AAD,CUSTOM")] //<-- yours schema
public class AccountController : Controller
{
// ...
}
Hope it helps you!!
回答2:
Possible things you could try:
1 Set up the default policy
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme, "AzureAD")
.RequireAuthenticatedUser()
.Build();
2 On the OnAuthenticationFailed > under one of the jwtOptions.Events, add a condition if it's authenticated then complete the task and don't show the error. Sometimes the user is authenticated already but the error from one provider prevents the proper response
if (arg.HttpContext.User.Identity.IsAuthenticated)
{
return Task.CompletedTask;
}
3 If this doesn't work. There's a hack to check if it's authenticated. Add more conditions per scheme.
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync("AzureAD");
if (result?.Principal != null)
{
context.User = result.Principal;
}
}
await next.Invoke();
});
来源:https://stackoverflow.com/questions/59327955/net-core-2-2-multiple-bearer-token-authentication-schemes