没有身份的 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 中的多个身份

ASP.NET Core 2.0 身份验证中间件

如何在 ASP.NET Core 2.0 中设置多个身份验证方案?

如何在 ASP.NET Core 2.0 中根据路由配置服务身份验证

asp.net core 2.0 授权在身份验证(JWT)之前触发