ASP.NET Core 2.0不带身份的承载身份验证

ASP.NET Core 2.0不带身份的承载身份验证,asp.net,asp.net-core,bearer-token,Asp.net,Asp.net Core,Bearer Token,当我一天前开始在.NETCore2.0上实现一个自包含的bearer auth webapi时,我想我有一个非常简单的目标,但我还没有实现任何远程工作。下面是我正在尝试做的事情的列表: 实现受承载令牌保护的webapi 发出令牌&从同一项目中的端点刷新令牌 使用[Authorize]属性控制对api表面的访问 不使用ASP.Net身份(我有更轻的用户/会员要求) 我完全可以在登录中构建identity/claims/principal并将其添加到请求上下文中,但我还没有看到一个关于如何在没有

当我一天前开始在.NETCore2.0上实现一个自包含的bearer auth webapi时,我想我有一个非常简单的目标,但我还没有实现任何远程工作。下面是我正在尝试做的事情的列表:

  • 实现受承载令牌保护的webapi
  • 发出令牌&从同一项目中的端点刷新令牌
  • 使用[Authorize]属性控制对api表面的访问
  • 不使用ASP.Net身份(我有更轻的用户/会员要求)
我完全可以在登录中构建identity/claims/principal并将其添加到请求上下文中,但我还没有看到一个关于如何在没有identity的Core2.0WebAPI中发布和使用auth/refresh令牌的示例。我看过1.x MSDN中没有标识的cookies示例,但这并没有让我对满足上述要求有足够的了解

我觉得这可能是一个常见的场景,不应该这么难(也许不是,也许只是缺少文档/示例?)。据我所知,IdentityServer4与Core 2.0 Auth不兼容,OpenDidict似乎需要标识。我也不想在单独的进程中托管令牌端点,而是在同一个webapi实例中


有谁能给我举个具体的例子,或者至少给我一些关于什么是最佳步骤/选项的指导

进行了编辑,使其与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()中

这应该就是你所需要的了。希望我没有错过任何东西

令人高兴的结果是

[Authorize] // Yay!
[Route("api/values")]
public class ValuesController : Controller
{
    // ...
}

在@Mitch answer之后:Auth stack在迁移到.NETCore2.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" };
        }
    }
}
使用系统;
使用System.IdentityModel.Tokens.Jwt;
使用System.Security.Claims;
使用系统文本;
使用System.Threading.Tasks;
使用JwtWithoutIdentity.Models;
使用Microsoft.AspNetCore.Authorization;
使用Microsoft.AspNetCore.Mvc;
使用Microsoft.IdentityModel.Tokens;
命名空间JwtWithoutIdentity.Controllers
{
公共类令牌控制器:控制器
{
[异名]
[路由(“api/令牌”)]
[HttpPost]
公共异步任务令牌(LoginViewModel模型)
{
如果(!ModelState.IsValid)返回BadRequest(“生成令牌失败”);
var user=(model.Password==“Password”和&model.Username==“Username”);
如果(!user)返回Unauthorized();
//添加索赔
风险值索赔=新[]
{
新索赔(JwtRegisteredClaimNames.UniqueName,“数据”),
新索赔(JwtRegisteredClaimNames.Sub,“数据”),
新声明(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
};
var key=new SymmetricSecurityKey(Encoding.UTF8.GetBytes(“rlyaKithdrYVl6Z80ODU350md”);//Secret
var creds=新的签名凭证(key,SecurityAlgorithms.HmacSha256);
var token=新的JwtSecurityToken(“我”,
“你”,
声称,
过期:DateTime.Now.AddMinutes(30),
签署证书:信誉);
返回Ok(新的JsonWebToken()
{
access_token=new JwtSecurityTokenHandler().WriteToken(令牌),
expires_in=600000,
令牌类型=“承载人”
});
}
}
}
值控制器

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" };
        }
    }
}
使用System.Collections.Generic;
使用Microsoft.AspNetCore.Authorization;
使用Microsoft.AspNetCore.Mvc;
命名空间JwtWithoutIdentity.Controllers
{
[路由(“api/[控制器]”)]
公共类值控制器:控制器
{
//获取api/值
[授权]
[HttpGet]
公共IEnumerable Get()
{
var name=User.Identity.name;
var索赔=User.claims;
返回新字符串[]{“value1”,“value2”};
}
}
}

希望这有帮助

我也很想看看这个例子。它与JWT机制是解耦的。读和读。您好。我甚至没有真正想过在所有关于新Core2 auth stack等的讨论中使用我自己的——我以为这将是现成的。在任何情况下,您的解决方案中唯一缺少的就是刷新令牌,但鉴于上述情况,这是微不足道的。一个问题-这些安全令牌是不透明的还是透明的?(即,当提供令牌时,身份验证堆栈是否会取消保护并将标识附加到webapi上下文,或者这是一个附加步骤?)谢谢Mitch!它解密令牌并为您设置上下文标识。在您的控制器中,
User.Identity.Name
将是传递到JWT中的用户名。是的,我还没有抽出时间刷新令牌-它的操作与JWT生成代码完全不同。通过一些随机散列生成令牌,存储它,并在刷新调用期间检查它。这段代码是为一个快速API编写的,我在.NET核心测试阶段不得不重新编写。如果有人在更新的功能上有一个更简单的实现,那就太好了