using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Newtonsoft.Json;
namespace Shared.Providers
{
public class AuthenticationProvider : OpenIdConnectServerProvider
{
private readonly IApplicationService _applicationservice;
private readonly IUserService _userService;
public AuthenticationProvider(IUserService userService,
IApplicationService applicationservice)
{
_applicationservice = applicationservice;
_userService = userService;
}
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (string.IsNullOrEmpty(context.ClientId))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Missing credentials: ensure that your credentials were correctly " +
"flowed in the request body or in the authorization header");
return Task.FromResult(0);
}
#region Validate Client
var application = _applicationservice.GetByClientId(context.ClientId);
if (applicationResult == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Application not found in the database: ensure that your client_id is correct");
return Task.FromResult(0);
}
else
{
var application = applicationResult.Data;
if (application.ApplicationType == (int)ApplicationTypes.JavaScript)
{
// Note: the context is marked as skipped instead of validated because the client
// is not trusted (JavaScript applications cannot keep their credentials secret).
context.Skip();
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Authorization server only handles Javascript application.");
return Task.FromResult(0);
}
}
#endregion Validate Client
return Task.FromResult(0);
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
var username = context.Request.Username.ToLowerInvariant();
var user = await _userService.GetUserLoginDtoAsync(
// filter
u => u.UserName == username
);
if (user == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid username or password.");
return;
}
var password = context.Request.Password;
var passWordCheckResult = await _userService.CheckUserPasswordAsync(user, context.Request.Password);
if (!passWordCheckResult)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid username or password.");
return;
}
var roles = await _userService.GetUserRolesAsync(user);
if (!roles.Any())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Invalid user configuration.");
return;
}
// add the claims
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, user.Id, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(ClaimTypes.Name, user.UserName, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
// add the user's roles as claims
foreach (var role in roles)
{
identity.AddClaim(ClaimTypes.Role, role, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
context.Validate(new ClaimsPrincipal(identity));
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
public override Task ApplyTokenResponse(ApplyTokenResponseContext context)
{
var token = context.Response.Root;
var stringified = JsonConvert.SerializeObject(token);
// the token will be stored in a cookie on the client
context.HttpContext.Response.Cookies.Append(
"exampleToken",
stringified,
new Microsoft.AspNetCore.Http.CookieOptions()
{
Path = "/",
HttpOnly = true, // to prevent XSS
Secure = false, // set to true in production
Expires = // your token life time
}
);
return base.ApplyTokenResponse(context);
}
}
}
public class AuthorizationHeader
{
private readonly RequestDelegate _next;
public AuthorizationHeader(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var authenticationCookieName = "exampleToken";
var cookie = context.Request.Cookies[authenticationCookieName];
if (cookie != null)
{
if (!context.Request.Path.ToString().ToLower().Contains("/account/logout"))
{
if (!string.IsNullOrEmpty(cookie))
{
var token = JsonConvert.DeserializeObject<AccessToken>(cookie);
if (token != null)
{
var headerValue = "Bearer " + token.access_token;
if (context.Request.Headers.ContainsKey("Authorization"))
{
context.Request.Headers["Authorization"] = headerValue;
}else
{
context.Request.Headers.Append("Authorization", headerValue);
}
}
}
await _next.Invoke(context);
}
else
{
// this is a logout request, clear the cookie by making it expire now
context.Response.Cookies.Append(authenticationCookieName,
"",
new Microsoft.AspNetCore.Http.CookieOptions()
{
Path = "/",
HttpOnly = true,
Secure = false,
Expires = DateTime.UtcNow.AddHours(-1)
});
context.Response.Redirect("/");
return;
}
}
else
{
await _next.Invoke(context);
}
}
}
在startup.cs的Configure()中:
// use the AuthorizationHeader middleware
app.UseMiddleware<AuthorizationHeader>();
// Add a new middleware validating access tokens.
app.UseOAuthValidation();
2 回答
我建议你看一下以下的链接 .
https://stormpath.com/blog/token-authentication-asp-net-core
它们将JWT令牌存储在仅http的cookie中以防止XSS攻击 .
然后,他们通过在Startup.cs中添加以下代码来验证cookie中的JWT令牌:
CustomJwtDataFormat()是这里定义的自定义格式:
另一个解决方案是编写一些自定义中间件来拦截每个请求,查看它是否有cookie,从cookie中提取JWT并在它到达控制器的Authorize过滤器之前动态添加Authorization标头 . 以下是一些适用于OAuth令牌的代码,以获取想法:
...其中AccessToken是以下类:
希望这可以帮助 .
注意:同样重要的是要注意这种做事方式(仅限http的cookie中的令牌)将有助于防止XSS攻击但是不能免受跨站点请求伪造(CSRF)攻击,因此您必须使用防伪令牌或设置自定义标头以防止这些 .
此外,如果您不进行任何内容清理,攻击者仍然可以运行XSS脚本代表用户发出请求,即使启用了仅http cookie和CRSF保护 . 但是,攻击者无法窃取仅包含令牌的http的cookie,攻击者也无法从第三方网站发出请求 .
因此,您仍应对用户生成的内容(如评论等)执行大量清理工作 .
编辑:在评论中写道,博客文章链接和代码是由OP自己在几天前提出这个问题后编写的 .
对于那些对另一种“cookie中的令牌”感兴趣的方法来减少XSS暴露,他们可以使用oAuth中间件,例如ASP.NET Core中的OpenId Connect Server .
在调用令牌提供程序以将令牌(ApplyTokenResponse())发送回客户端的方法中,您可以序列化令牌并将其存储到仅限http的cookie中:
然后,您需要确保每个请求都附加了cookie . 您还必须编写一些中间件来拦截cookie并将其设置为标头:
在startup.cs的Configure()中:
然后,您可以正常使用“授权”属性 .
此解决方案适用于api和mvc应用程序 . 但是对于ajax和fetch请求,你必须编写一些自定义中间件,它不会将用户重定向到登录页面而是返回401:
我成功实现了中间件(基于Darxtar的答案):