没有身份的 ASP.NET Core 2.0 承载身份验证
Posted
技术标签:
【中文标题】没有身份的 ASP.NET Core 2.0 承载身份验证【英文标题】:ASP.NET Core 2.0 Bearer Auth without Identity 【发布时间】:2018-01-24 16:55:17 【问题描述】:当我一天前开始在 .NET core 2.0 上实现一个独立的承载身份验证 webapi 时,我以为我有一个非常简单的目标,但我还没有让任何远程工作。这是我正在尝试做的事情的列表:
实现不记名令牌保护的 webapi 从同一项目中的端点发布令牌和刷新令牌 使用 [Authorize] 属性控制对 api 表面的访问 不使用 ASP.Net Identity(我的用户/会员资格要求更轻)我完全可以在登录中构建身份/声明/主体并将其添加到请求上下文中,但我还没有看到一个关于如何在没有身份的 Core 2.0 webapi 中发布和使用身份验证/刷新令牌的示例.我已经看过没有身份的 1.x MSDN cookie 示例,但这并没有让我理解得足够远,无法满足上述要求。
我觉得这可能是一个常见的场景,它不应该这么难(也许不是,也许只是缺少文档/示例?)。据我所知,IdentityServer4 与 Core 2.0 Auth 不兼容,opendiddict 似乎需要 Identity。我也不想在单独的进程中托管令牌端点,而是在同一个 webapi 实例中。
谁能指出一个具体的例子,或者至少就最佳步骤/选项提供一些指导?
【问题讨论】:
我也很想看看这个示例。 身份与 JWT 机制解耦。阅读this 和this。问候。 【参考方案1】:进行了编辑以使其与 ASP.NET Core 2.0 兼容。
首先,一些 Nuget 包:
Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.AspNetCore.Identity System.IdentityModel.Tokens.Jwt System.Security.Cryptography.Csp然后是一些基本的数据传输对象。
// Presumably you will have an equivalent user account class with a user name.
public class User
public string UserName get; set;
public class JsonWebToken
public string access_token get; set;
public string token_type get; set; = "bearer";
public int expires_in get; set;
public string refresh_token get; set;
要使用正确的功能,您需要一个登录/令牌 Web 方法来实际将授权令牌发送给用户。
[Route("api/token")]
public class TokenController : Controller
private ITokenProvider _tokenProvider;
public TokenController(ITokenProvider tokenProvider) // We'll create this later, don't worry.
_tokenProvider = tokenProvider;
public JsonWebToken Get([FromQuery] string grant_type, [FromQuery] string username, [FromQuery] string password, [FromQuery] string refresh_token)
// Authenticate depending on the grant type.
User user = grant_type == "refresh_token" ? GetUserByToken(refresh_token) : GetUserByCredentials(username, password);
if (user == null)
throw new UnauthorizedAccessException("No!");
int ageInMinutes = 20; // However long you want...
DateTime expiry = DateTime.UtcNow.AddMinutes(ageInMinutes);
var token = new JsonWebToken
access_token = _tokenProvider.CreateToken(user, expiry),
expires_in = ageInMinutes * 60
;
if (grant_type != "refresh_token")
token.refresh_token = GenerateRefreshToken(user);
return token;
private User GetUserByToken(string refreshToken)
// TODO: Check token against your database.
if (refreshToken == "test")
return new User UserName = "test" ;
return null;
private User GetUserByCredentials(string username, string password)
// TODO: Check username/password against your database.
if (username == password)
return new User UserName = username ;
return null;
private string GenerateRefreshToken(User user)
// TODO: Create and persist a refresh token.
return "test";
您可能注意到令牌创建仍然只是由一些虚构的 ITokenProvider 传递的“魔法”。定义令牌提供者接口。
public interface ITokenProvider
string CreateToken(User user, DateTime expiry);
// TokenValidationParameters is from Microsoft.IdentityModel.Tokens
TokenValidationParameters GetValidationParameters();
我在 JWT 上使用 RSA 安全密钥实现了令牌创建。所以...
public class RsaJwtTokenProvider : ITokenProvider
private RsaSecurityKey _key;
private string _algorithm;
private string _issuer;
private string _audience;
public RsaJwtTokenProvider(string issuer, string audience, string keyName)
var parameters = new CspParameters KeyContainerName = keyName ;
var provider = new RSACryptoServiceProvider(2048, parameters);
_key = new RsaSecurityKey(provider);
_algorithm = SecurityAlgorithms.RsaSha256Signature;
_issuer = issuer;
_audience = audience;
public string CreateToken(User user, DateTime expiry)
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "jwt"));
// TODO: Add whatever claims the user may have...
SecurityToken token = tokenHandler.CreateJwtSecurityToken(new SecurityTokenDescriptor
Audience = _audience,
Issuer = _issuer,
SigningCredentials = new SigningCredentials(_key, _algorithm),
Expires = expiry.ToUniversalTime(),
Subject = identity
);
return tokenHandler.WriteToken(token);
public TokenValidationParameters GetValidationParameters()
return new TokenValidationParameters
IssuerSigningKey = _key,
ValidAudience = _audience,
ValidIssuer = _issuer,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(0) // Identity and resource servers are the same.
;
所以您现在正在生成令牌。是时候实际验证它们并将其连接起来了。转到您的 Startup.cs。
在ConfigureServices()
var tokenProvider = new RsaJwtTokenProvider("issuer", "audience", "mykeyname");
services.AddSingleton<ITokenProvider>(tokenProvider);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = tokenProvider.GetValidationParameters();
);
// This is for the [Authorize] attributes.
services.AddAuthorization(auth =>
auth.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
);
然后Configure()
public void Configure(IApplicationBuilder app)
app.UseAuthentication();
// Whatever else you're putting in here...
app.UseMvc();
这应该就是你所需要的。希望我没有错过任何东西。
幸福的结果是……
[Authorize] // Yay!
[Route("api/values")]
public class ValuesController : Controller
// ...
【讨论】:
我什至没有真正想过用新的 Core2 身份验证堆栈等的所有讨论来推出自己的产品 - 我认为这将是现成的。在任何情况下,您的解决方案中唯一缺少的是刷新令牌,但鉴于上述情况,这是微不足道的。一个问题 - 这些安全令牌是不透明的还是透明的? (即,当出现令牌时,身份验证堆栈是否取消保护并将身份附加到 webapi 上下文,或者这是一个额外的步骤?)谢谢米奇! 它解密令牌并为您设置上下文身份。在您的控制器中,User.Identity.Name
将是传递给 JWT 的用户名。
是的,我还没来得及刷新令牌——不过它的运行与 JWT 生成代码完全不同。通过一些随机哈希生成令牌,存储它,并在刷新调用期间检查它。这段代码是我在 .NET Core beta 阶段必须重新编写的快速 API。如果有人对新功能有更简单的实现,那就太好了。
在 Configure() 方法中,我收到一个错误,表明 UseJwtBearerAuthentication() 已过时,它引用了一些令人费解的文章(读起来更像是仍在实施过程中的 MS 编码员之间的内部讨论这)。我不想使用任何类型的身份服务器。有人请告诉我完成这项工作的唯一方法不是回滚到 asp.net core 1.1。 MS:你为什么一直这样对我们?
请注意,在 Mac OS X 上使用 .NET Core 2 时,当涉及“var provider = new RSACryptoServiceProvider(2048, parameters);”时,您将收到 System.PlatformNotSupportedException。 *** 上有关于这个问题的单独线程 ....【参考方案2】:
在@Mitch 回答之后:身份验证堆栈发生了很大变化,迁移到 .NET Core 2.0。下面的答案只是使用新的实现。
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity
public class Startup
public Startup(IConfiguration configuration)
Configuration = configuration;
public IConfiguration Configuration get;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
ValidIssuer = "me",
ValidAudience = "you",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
;
);
services.AddMvc();
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseAuthentication();
app.UseMvc();
令牌控制器
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JwtWithoutIdentity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity.Controllers
public class TokenController : Controller
[AllowAnonymous]
[Route("api/token")]
[HttpPost]
public async Task<IActionResult> Token(LoginViewModel model)
if (!ModelState.IsValid) return BadRequest("Token failed to generate");
var user = (model.Password == "password" && model.Username == "username");
if (!user) return Unauthorized();
//Add Claims
var claims = new[]
new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
new Claim(JwtRegisteredClaimNames.Sub, "data"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
;
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("me",
"you",
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new JsonWebToken()
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = 600000,
token_type = "bearer"
);
值控制器
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JwtWithoutIdentity.Controllers
[Route("api/[controller]")]
public class ValuesController : Controller
// GET api/values
[Authorize]
[HttpGet]
public IEnumerable<string> Get()
var name = User.Identity.Name;
var claims = User.Claims;
return new string[] "value1", "value2" ;
希望这会有所帮助!
【讨论】:
感谢您发布此信息。正在寻找类似的东西。我只是想知道如果您不使用User : Identity
,为什么您仍然拥有声明和身份。以上是关于没有身份的 ASP.NET Core 2.0 承载身份验证的主要内容,如果未能解决你的问题,请参考以下文章
在ASP.NET Core 2.0中使用Facebook进行身份验证
如何在 ASP.NET Core 2.0 中设置多个身份验证方案?