我有一些api端点(.net core mvc),如果该用户已登录,则将使用已登录的用户(JWT),但任何人都可以调用该方法。
通过将[AllowAnonymous]
与[Authorize]
一起使用,可以得到所需的功能,但是如果发送的JWT过期,则不会得到401,而是将其视为异常请求。< / p>
仅当存在Authorization:Bearer标头时,我才需要授权逻辑将端点视为[Authorize]
,这意味着如果令牌已过期,则应返回401
此功能仅在少数端点上才需要,而在完整的控制器上则不需要
我尝试了[AllowAnonymous]
+ [Authorize]
的组合。
在创建策略时也尝试过RequireAssertion
,但似乎并没有想到
我用于测试的方法:
[HttpPost]
[Route("testToken")]
[AllowAnonymous]
[Authorize(Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER)]
public async Task<IActionResult> testToken()
{
var user = await _signInManager.UserManager.GetUserAsync(HttpContext.User);
return Ok(new {result = user});
}
设置身份验证以支持cookie + jwt:
services.AddAuthorization(o =>
{
o.AddPolicy(AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER, b =>
{
b.RequireRole("Admin");
b.RequireAuthenticatedUser();
b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
});
});
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(cfg =>
{
var issuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
var tokenKey = Environment.GetEnvironmentVariable("JWT_TOKEN_KEY");
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidIssuer = issuer,
ValidAudience = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey))
};
}
);
我希望[Authorize]
标头在使用过期令牌而不是将方法作为匿名方法调用时返回401 Unauthorized。这可以设置吗?
更新 我创建了这样的属性:
public class AuthorizeBearerWhenPresent : ActionFilterAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var headers = context.HttpContext.Request;
//TODO: Test header
var httpContext = context.HttpContext;
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var authResult = await authService.AuthorizeAsync(httpContext.User, context,
AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER);
if (!authResult.Succeeded)
{
context.Result = new UnauthorizedResult();
}
}
}
但是,无论我向authService
发送什么内容,它都将返回true。我发送无效的JWT标头还是不发送标头都没有关系。这不是执行策略的正确方法吗?
答案 0 :(得分:1)
谢谢您的建议。在实施由Bart van der Drift发布的解决方案时,我偶然发现了自定义标头和IAuthorizationPolicyProvider
这样,我使用的是自定义策略名称,然后将其覆盖:
AuthFilterConvension
中的常量:
public const string POLICY_JWT = "jwtPolicy";
public const string POLICY_AUTHORIZE_WHEN_HAS_BEARER = "authorizeWhenHasBearer";
设置策略:
services.AddAuthorization(o =>
{
o.AddPolicy(AuthFilterConvension.POLICY_JWT, b =>
{
b.RequireRole("Admin");
b.RequireAuthenticatedUser();
b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
});
});
添加自定义属性:
public class AuthorizeBearerWhenPresent : AuthorizeAttribute
{
public AuthorizeBearerWhenPresent()
{
Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER;
}
}
未配置名称POLICY_AUTHORIZE_WHEN_HAS_BEARER,仅将其用作我的CustomPolicyProvicer
中的密钥:
public class CustomPolicyProvider : IAuthorizationPolicyProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
public CustomPolicyProvider(IHttpContextAccessor httpContextAccessor, IOptions<AuthorizationOptions> options)
{
_httpContextAccessor = httpContextAccessor;
_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER.Equals(policyName))
{
if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
{
return await _fallbackPolicyProvider.GetPolicyAsync(AuthFilterConvension.POLICY_JWT);
}
return new AuthorizationPolicyBuilder()
.RequireAssertion(x=>true)
.Build();
}
return await _fallbackPolicyProvider.GetPolicyAsync(policyName);
}
public async Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return await _fallbackPolicyProvider.GetDefaultPolicyAsync();
}
}
这样,我可以避免对JWT令牌进行自定义处理
以下内容:
return new AuthorizationPolicyBuilder()
.RequireAssertion(x=>true)
.Build();
仅用作虚拟“允许所有”
答案 1 :(得分:0)
您可以编写一个自定义中间件来这样做,例如:
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IConfiguration configuration)
{
if (httpContext.Request.Headers.ContainsKey("Authorization"))
{
var authorizationToken = httpContext.Request.Headers["Authorization"].ToString();
if (!authorizationToken.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
{
await UnauthorizedResponseAsync(httpContext);
}
else
{
var token =authorizationToken.Substring("Bearer".Length).Trim())
if (httpContext.Request.Path == "Some of your path")
{
// DO your stuff
await _next.Invoke(httpContext);
}
}
}
else
{
await UnauthorizedResponseAsync(httpContext);
}
}
private static async Task UnauthorizedResponseAsync(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
await httpContext.Response.WriteAsync("Unauthorized");
return;
}
答案 2 :(得分:0)
您可以通过实现IAuthenticationFilter
接口并从ActionFilterAttribute
继承来创建自己的身份验证逻辑:
public class MyCustomAuthentication : ActionFilterAttribute, System.Web.Http.Filters.IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// Handle the authorization header
}
}
然后在控制器中,您可以将属性添加到类或特定方法中。
[MyCustomAuthentication]
public async Task<IHttpActionResult> DoSomethingAsync()
{
// ...
}
如果存在令牌,则还必须手动验证令牌。 下面的代码基于.Net Framework,因此不确定它是否也适用于Core。
// Build URL based on your AAD-TenantId
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", "<Your_tenant_ID>");
// Get tenant information that's used to validate incoming jwt tokens
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
// Get Config from AAD:
var config = await configManager.GetConfigurationAsync();
// Validate token:
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = "<Client_ID>",
ValidIssuer = "<Issuer>",
IssuerSigningTokens = config.SigningTokens,
CertificateValidator = X509CertificateValidator.ChainTrust
};
var parsedToken = (System.IdentityModel.Tokens.SecurityToken)new JwtSecurityToken();
try
{
tokenHandler.ValidateToken(token, validationParameters, out parsedToken);
result.ValidatedToken = (JwtSecurityToken)parsedToken;
}
catch (System.IdentityModel.Tokens.SecurityTokenValidationException stve)
{
// Handle error using stve.Message
}