我想在我的 MVC 应用程序中进行 JWT 身份验证。我在 Web API 中创建授权 Web 服务,它正确返回令牌。之后我尝试将令牌存储在 cookie 中。
[HttpPost]
public async Task<ActionResult> Login(LoginDto loginDto)
{
var token = await loginService.GetToken(loginDto);
if (!string.IsNullOrEmpty(token))
{
var cookie = new System.Web.HttpCookie("token", token)
{
HttpOnly = true
};
Response.Cookies.Add(cookie);
return RedirectToAction("Index", "Product");
}
return View("LoginFailed");
}
但现在我想将此令牌添加到每个请求的标头中。所以我认为动作过滤器最适合实现这一目标。
public class CustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var token = filterContext.HttpContext.Request.Cookies.Get("token");
if (token != null)
filterContext.HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");
base.OnActionExecuting(filterContext);
}
}
启动
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
});
}
}
然后我只是标记了授权属性的控制器。当我用 POSTMAN 调用它时它工作正常。
但是 MVC 中的操作过滤器总是在授权过滤器之后触发。所以我有疑问:
这就是登录服务的样子。它只是调用身份验证端点。
public class LoginService : ILoginService
{
public async Task<string> GetToken(LoginDto loginDto)
{
var tokenIssuer = ConfigurationManager.AppSettings["issuer"];
using (var httpClient = new HttpClient {BaseAddress = new Uri($"{tokenIssuer}/oauth2/token")})
{
using (var response = await httpClient.PostAsync(httpClient.BaseAddress, new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", loginDto.Username),
new KeyValuePair<string, string>("password", loginDto.Password),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_id", ConfigurationManager.AppSettings["appId"])
})))
{
var contents = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
var deserializedResponse =
new JavaScriptSerializer().Deserialize<Dictionary<string, string>>(contents);
var token = deserializedResponse["access_token"];
return token;
}
}
return null;
}
}
}
我找到了解决办法。我只是制作自定义
OAuthBearerAuthenticationProvider
提供程序,并在此类中从 cookie 检索令牌,然后将其分配给 context.Token
public class MvcJwtAuthProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var token = context.Request.Cookies.SingleOrDefault(x => x.Key == "token").Value;
context.Token = token;
return base.RequestToken(context);
}
}
然后在startup.cs里面
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new MvcJwtAuthProvider() // override custom auth
});
}
}
**A little late to the party but**
builder.Services.AddAuthentication(选项=> { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer( 选择 => { opt.RequireHttpsMetadata = false; opt.SaveToken = true; opt.TokenValidationParameters = new TokenValidationParameters() { ValidIssuer = tokenOptions.Issuer, 验证发行者 = true, IssuerSigningKey = SignService.GetSymmetricSecurityKey(tokenOptions.SecurityKey), ValidAudience = tokenOptions.Audience[0], 验证受众 = true, ValidateIssuerSigningKey = true, 验证生命周期 = true, RoleClaimType = ClaimTypes.Role }; opt.Events = new JwtBearerEvents() { OnAuthenticationFailed = 上下文 => {
context.Response.Redirect("/login");
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
if (!context.Request.Cookies.ContainsKey("session"))
return Task.CompletedTask;
context.Token = context.Request.Cookies["session"];
context.Request.Headers.TryAdd("Bearer", context.Token); // this line will add cokie's value to header automaticly
return Task.CompletedTask;
},
};
}).AddCookie( options =>
{
options.Cookie.Name = "session";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.ExpireTimeSpan = TimeSpan.FromMinutes(tokenOptions.AccessTokenExpiration);
options.LoginPath = "/login";
options.AccessDeniedPath = "/login";
});
**或者,您可以将此行添加到控制器中,它将自动尝试两种验证方法。在这种情况下,您可以忽略 context.Request.Headers.TryAdd("Bearer", context.Token);部分**
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]