C# 在JWT资源服务器应用程序客户端(访问群体)中动态追加OW
我有一个C# 在JWT资源服务器应用程序客户端(访问群体)中动态追加OW,c#,oauth,owin,jwt,C#,Oauth,Owin,Jwt,我有一个C#API,它使用OWINJWT进行身份验证 Mystartup.cs(我的资源服务器)根据代码配置OAuth: public void ConfigureOAuth(IAppBuilder app) { var issuer = "<the_same_issuer_as_AuthenticationServer.Api>"; // Api controllers with an [Authorize] attribute will be validated
C#
API,它使用OWINJWT进行身份验证
Mystartup.cs
(我的资源服务器)根据代码配置OAuth:
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = "<the_same_issuer_as_AuthenticationServer.Api>";
// Api controllers with an [Authorize] attribute will be validated with JWT
var audiences = DatabaseAccessLayer.GetAllowedAudiences(); // Gets a list of audience Ids, secrets, and names (although names are unused)
// List the
List<string> audienceId = new List<string>();
List<IIssuerSecurityTokenProvider> providers = new List<IIssuerSecurityTokenProvider>();
foreach (var aud in audiences) {
audienceId.Add(aud.ClientId);
providers.Add(new SymmetricKeyIssuerSecurityTokenProvider(issuer, TextEncodings.Base64Url.Decode(aud.ClientSecret)));
}
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = audienceId.ToArray(),
IssuerSecurityTokenProviders = providers.ToArray(),
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = context =>
{
context.Ticket.Identity.AddClaim(new System.Security.Claims.Claim("newCustomClaim", "newValue"));
return Task.FromResult<object>(null);
}
}
});
}
public void配置OAuth(IAppBuilder应用程序)
{
var发行人=”;
//具有[Authorize]属性的Api控制器将使用JWT进行验证
var audients=DatabaseAccessLayer.getAllowedAudients();//获取访问群体ID、机密和名称的列表(尽管名称未使用)
//列出
List audienceId=新列表();
列表提供程序=新列表();
foreach(观众中的var aud){
audenceId.Add(aud.ClientId);
添加(新的SymmetriceIsuerSecurityTokenProvider(颁发者,textcodections.Base64Url.Decode(aud.ClientSecret));
}
app.UseJwtBearerAuthentication(
新的JWTBeareAuthenticationOptions
{
AuthenticationMode=AuthenticationMode.Active,
AllowedAudients=audienceId.ToArray(),
IssuerSecurityTokenProviders=providers.ToArray(),
Provider=新的OAuthBeareAuthenticationProvider
{
OnValidateIdentity=上下文=>
{
context.Ticket.Identity.AddClaim(新系统.Security.Claims.Claim(“newCustomClaim”、“newValue”));
返回Task.FromResult(空);
}
}
});
}
它允许在多个clientid中检查经过身份验证的承载令牌。这很有效。
但是,我的web应用程序允许用户创建新的应用程序访问群体(即新的ClientID、ClientSecret、和ClientName组合),但在此之后,我不知道如何让资源服务器的JwtBearerAuthenticationOptions
识别新创建的访问者
我可以在新访问群体之后重新启动服务器,以便ConfigureOAuth()
在新访问群体之后重新运行,但从长远来看,这不是一个好方法
是否有人知道如何在startup.cs和ConfigureOAuth()之外向OWIN应用程序jwtbeareraauthenticationoptions
添加访问群体(即新的**ClientID
、ClientSecret和ClientName组合)**
我一直在寻求:和帮助,但这两个代码示例都显示了上述相同的问题。在使用X509CertificateSecurityTokenProvider时,以下功能有效。它已被修改为使用SymmetriceIsuerSecurityTokenProvider,但尚未经过测试
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = "<the_same_issuer_as_AuthenticationServer.Api>";
// Api controllers with an [Authorize] attribute will be validated with JWT
Func<IEnumerable<Audience>> allowedAudiences = () => DatabaseAccessLayer.GetAllowedAudiences();
var bearerOptions = new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(new TokenValidationParameters
{
AudienceValidator = (audiences, securityToken, validationParameters) =>
{
return allowedAudiences().Select(x => x.ClientId).Intersect(audiences).Count() > 0;
},
ValidIssuers = new ValidIssuers { Audiences = allowedAudiences },
IssuerSigningTokens = new SecurityTokensTokens(issuer) { Audiences = allowedAudiences }
})
};
app.UseOAuthBearerAuthentication(bearerOptions);
}
public abstract class AbstractAudiences<T> : IEnumerable<T>
{
public Func<IEnumerable<Audience>> Audiences { get; set; }
public abstract IEnumerator<T> GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public class SecurityTokensTokens : AbstractAudiences<SecurityToken>
{
private string issuer;
public SecurityTokensTokens(string issuer)
{
this.issuer = issuer;
}
public override IEnumerator<SecurityToken> GetEnumerator()
{
foreach (var aud in Audiences())
{
foreach (var securityToken in new SymmetricKeyIssuerSecurityTokenProvider(issuer, TextEncodings.Base64Url.Decode(aud.ClientSecret)).SecurityTokens)
{
yield return securityToken;
};
}
}
}
public class ValidIssuers : AbstractAudiences<string>
{
public override IEnumerator<string> GetEnumerator()
{
foreach (var aud in Audiences())
{
yield return aud.ClientSecret;
}
}
}
public void配置OAuth(IAppBuilder应用程序)
{
var发行人=”;
//具有[Authorize]属性的Api控制器将使用JWT进行验证
Func allowedAudients=()=>DatabaseAccessLayer.getAllowedAudients();
var bearerOptions=新的OAuthBeareAuthenticationOptions
{
AccessTokenFormat=新JwtFormat(新TokenValidationParameters
{
AudienceValidator=(受众、securityToken、validationParameters)=>
{
返回AllowedAudients();
},
ValidisUsers=新ValidisUsers{观众=允许观众},
IssuerSigningTokens=新证券Tokens(发行人){受众=允许受众}
})
};
应用程序UseAuthBeareAuthentication(BeareOptions);
}
公共抽象类AbstractAudits:IEnumerable
{
公共函数访问对象{get;set;}
公共抽象IEnumerator GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
抛出新的NotImplementedException();
}
}
公共类安全令牌斯托克斯:抽象受众
{
私人字符串发行人;
公共安全Tokenstokens(字符串发行人)
{
this.issuer=发行人;
}
公共重写IEnumerator GetEnumerator()
{
foreach(受众中的变量aud())
{
foreach(新SymmetriceIsuerSecurityTokenProvider中的var securityToken(发行者,textcodections.Base64Url.Decode(aud.ClientSecret)).SecurityTokens)
{
收益回报证券代币;
};
}
}
}
公共类ValidIssuers:抽象受众
{
公共重写IEnumerator GetEnumerator()
{
foreach(受众中的变量aud())
{
收益回报率aud.ClientSecret;
}
}
}
}我会尽力帮忙的,不过:D在心里我是个初学者,所以它可能不是最好的:D 我还希望在不重新启动服务的情况下拥有动态的受众,因为最终它会带来灵活性和易用性 因此,我的确认如下:
var bearerOptions = new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(new TokenValidationParameters
{
AudienceValidator = AudienceValidator,
IssuerSigningToken = x509SecToken,
ValidIssuer = issuer,
RequireExpirationTime = true,
ValidateLifetime = true,
})
};
app.UseOAuthBearerAuthentication(bearerOptions);
正如您在上面看到的,我确实有一个代表在验证我的受众。这基本上意味着什么——每次您向服务器发出请求时,都会调用此方法来验证访问群体
目前,我只有一些小的调试方法,我正在验证任何进入的观众:
private bool AudienceValidator(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
Trace.Write("would be validating audience now");
return true;
}
private bool AudienceValidator(IEnumerable受众、SecurityToken SecurityToken、TokenValidationParameters validationParameters)
{
Trace.Write(“现在将验证受众”);
返回true;
}
现在下一步是在这里做什么?当然,您不希望每次验证受众时都查询DB,因为这会忽略使用这些令牌的目的:D您可能会想出一些好主意-请分享
第一种方法:
因此,我所做的是使用,并计划每1小时更新一次DB中的AllowedAudions。这很有效。我正在用一套权限注册新的观众,在最好的情况下,他们已经准备好飞了,或者我必须等待大约59分钟:)
希望这有帮助
第二种方法:
现在是第二次
/// <summary>
/// The B2C token handler for handling dynamically loaded B2C tenants.
/// </summary>
protected B2CTokenHandler TokenHandler = new B2CTokenHandler();
/// <summary>
/// Setup the OAuth authentication. We use the database to retrieve the available B2C tenants.
/// </summary>
/// <param name="app">The application builder object</param>
public AuthOAuth2(IAppBuilder app) {
// get Active Directory endpoint
AadInstance = ConfigurationManager.AppSettings["b2c:AadInstance"];
// get the B2C policy list used by API1
PolicyIdList = ConfigurationManager.AppSettings["b2c:PolicyIdList"].Split(',').Select(p => p.Trim()).ToList();
TokenValidationParameters tvps = new TokenValidationParameters {
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier"
};
// create a access token format
JwtFormat jwtFormat = new JwtFormat(tvps);
// add our custom token handler which will provide token validation parameters per tenant
jwtFormat.TokenHandler = TokenHandler;
// wire OAuth authentication for tenants
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {
// the security token provider handles Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = jwtFormat,
Provider = new OAuthBearerAuthenticationProvider() {
OnValidateIdentity = async (context) => await OAuthValidateIdentity(context)
}
});
// load initial OAuth authentication tenants
LoadAuthentication();
}
/// <summary>
/// Load the OAuth authentication tenants. We maintain a local hash map of those tenants during
/// processing so we can track those tenants no longer in use.
/// </summary>
protected override void LoadAuthentication() {
AuthProcessing authProcessing = new AuthProcessing();
List<B2CAuthTenant> authTenantList = new List<B2CAuthTenant>();
// add all tenants for authentication
foreach (AuthTenantApp authTenantApp in authProcessing.GetAuthTenantsByAppId("API1")) {
// create a B2C authentication tenant per policy. Note that the policy may not exist, and
// this will be handled by the B2C token handler at configuration load time below
foreach (string policyId in PolicyIdList) {
authTenantList.Add(new B2CAuthTenant {
Audience = authTenantApp.ClientId,
PolicyId = policyId,
TenantName = authTenantApp.Tenant
});
}
}
// and load the token handler with the B2C authentication tenants
TokenHandler.LoadConfiguration(AadInstance, authTenantList);
// we must update the CORS origins
string origins = string.Join(",", authProcessing.GetAuthTenantAuthoritiesByAppId("API1").Select(a => a.AuthorityUri));
// note some browsers do not support wildcard for exposed headers - there specific needed. See
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers#Browser_compatibility
EnableCorsAttribute enableCors = new EnableCorsAttribute(origins, "*", "*", "Content-Disposition");
enableCors.SupportsCredentials = true;
enableCors.PreflightMaxAge = 30 * 60;
GlobalConfiguration.Configuration.EnableCors(enableCors);
}
/// <summary>
/// Dictionary of currently configured OAuth audience+policy to the B2C endpoint signing key cache.
/// </summary>
protected ConcurrentDictionary<string, OpenIdConnectCachingSecurityTokenProvider> AudiencePolicyMap = new ConcurrentDictionary<string, OpenIdConnectCachingSecurityTokenProvider>();
/// <summary>
/// Load the B2C authentication tenant list, creating a B2C endpoint security token provider
/// which will bethe source of the token signing keys.
/// </summary>
/// <param name="aadInstance">The Active Directory instance endpoint URI</param>
/// <param name="b2cAuthTenantList">The B2C authentication tenant list</param>
public void LoadConfiguration(string aadInstance, List<B2CAuthTenant> b2cAuthTenantList) {
// maintain a list of keys that are loaded
HashSet<string> b2cAuthTenantSet = new HashSet<string>();
// attempt to create a security token provider for each authentication tenant
foreach(B2CAuthTenant b2cAuthTenant in b2cAuthTenantList) {
// form the dictionary key
string tenantKey = $"{b2cAuthTenant.Audience}:{b2cAuthTenant.PolicyId}";
if (!AudiencePolicyMap.ContainsKey(tenantKey)) {
try {
// attempt to create a B2C endpoint security token provider. We may fail if there is no policy
// defined for that tenant
OpenIdConnectCachingSecurityTokenProvider tokenProvider = new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, b2cAuthTenant.TenantName, b2cAuthTenant.PolicyId));
// add to audience:policy map
AudiencePolicyMap[tenantKey] = tokenProvider;
// this guy is new
b2cAuthTenantSet.Add(tenantKey);
} catch (Exception ex) {
// exception has already been reported appropriately
}
} else {
// this guys is already present
b2cAuthTenantSet.Add(tenantKey);
}
}
// at this point we have a set of B2C authentication tenants that still exist. Remove any that are not
foreach (KeyValuePair<string, OpenIdConnectCachingSecurityTokenProvider> kvpAudiencePolicy in AudiencePolicyMap.Where(t => !b2cAuthTenantSet.Contains(t.Key))) {
AudiencePolicyMap.TryRemove(kvpAudiencePolicy.Key, out _);
}
}
/// <summary>
/// Validate a security token. We are responsible for priming the token validation parameters
/// with the specific parameters for the audience:policy, if found.
/// </summary>
/// <param name="securityToken">A 'JSON Web Token' (JWT) that has been encoded as a JSON object. May be signed using 'JSON Web Signature' (JWS)</param>
/// <param name="tvps">Contains validation parameters for the security token</param>
/// <param name="validatedToken">The security token that was validated</param>
/// <returns>A claims principal from the jwt. Does not include the header claims</returns>
public override ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters tvps, out SecurityToken validatedToken) {
if (string.IsNullOrWhiteSpace(securityToken)) {
throw new ArgumentNullException("Security token is null");
}
// decode the token as we need the 'aud' and 'tfp' claims
JwtSecurityToken token = ReadToken(securityToken) as JwtSecurityToken;
if (token == null) {
throw new ArgumentOutOfRangeException("Security token is invalid");
}
// get the audience and policy
Claim audience = token.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Aud);
Claim policy = token.Claims.FirstOrDefault(c => c.Type == ClaimTypesB2C.Tfp);
if ((audience == null) || (policy == null)) {
throw new SecurityTokenInvalidAudienceException("Security token has no audience/policy id");
}
// generate the key
string tenantKey = $"{audience.Value}:{policy.Value}";
// check if this audience:policy is known
if (!AudiencePolicyMap.ContainsKey(tenantKey)) {
throw new SecurityTokenInvalidAudienceException("Security token has unknown audience/policy id");
}
// get the security token provider
OpenIdConnectCachingSecurityTokenProvider tokenProvider = AudiencePolicyMap[tenantKey];
// clone the token validation parameters so we can update
tvps = tvps.Clone();
// we now need to prime the validation parameters for this audience
tvps.ValidIssuer = tokenProvider.Issuer;
tvps.ValidAudience = audience.Value;
tvps.AuthenticationType = policy.Value;
tvps.IssuerSigningTokens = tokenProvider.SecurityTokens;
// and call real validator with updated parameters
return base.ValidateToken(securityToken, tvps, out validatedToken);
}
/// <summary>
/// Retrieve the metadata from the endpoint.
/// </summary>
private void RetrieveMetadata() {
metadataLock.EnterWriteLock();
try {
// retrieve the metadata
OpenIdConnectConfiguration config = Task.Run(configManager.GetConfigurationAsync).Result;
// and update
issuer = config.Issuer;
securityTokens = config.SigningTokens;
} catch (Exception ex) when (CheckHttp404(ex)) {
// ignore 404 errors as they indicate that the policy does not exist for a tenant
logger.Warn($"Policy endpoint not found for {metadataEndpoint} - ignored");
throw ex;
} catch (Exception ex) {
logger.Fatal(ex, $"System error in retrieving token metadatafor {metadataEndpoint}");
throw ex;
} finally {
metadataLock.ExitWriteLock();
}
}
/// <summary>
/// Check if the inner most exception is a HTTP response with status code of Not Found.
/// </summary>
/// <param name="ex">The exception being examined for a 404 status code</param>
/// <returns></returns>
private bool CheckHttp404(Exception ex) {
// get the inner most exception
while(ex.InnerException != null) {
ex = ex.InnerException;
}
// check if a HttpWebResponse with a 404
return (ex is WebException webex) && (webex.Response is HttpWebResponse response) && (response.StatusCode == HttpStatusCode.NotFound);
}