当我一天前开始在上实现一个自包含的Bear auth webapi时,我想我有一个非常简单的目标.NET core 2.0,但我还没有任何远程工作.以下是我正在try 做的事情:

  • 实现受承载令牌保护的webapi
  • 发行 token ;刷新同一项目中端点的令牌
  • 使用[Authorize]属性控制对api表面的访问
  • 不使用ASP.NET标识(我的用户/成员资格要求要轻得多)

我完全可以在登录中构建identity/claims/principal,并将其添加到请求上下文中,但我还没有看到一个关于如何在没有identity的Core 2.0 webapi中发布和使用auth/refresh令牌的示例.我看过1.x MSDN没有标识的Cookie示例,但这并没有让我对满足上述要求有足够的了解.

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

有没有人能给我举一个具体的例子,或者至少给我一些指导,告诉我什么是最好的步骤/选项?

推荐答案

进行了编辑,使其与ASP兼容.NET核心2.0.


首先是一些Nuget包:

  • 微软AspNetCore.认证.JWTBearner
  • Microsoft.AspNetCore.Identity
  • 系统IdentityModel. token .Jwt
  • 系统安全密码学.顾客服务Provider

然后是一些基本的数据传输对象.

// 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 = Security算法rithms.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
{
    // ...
}

Asp.net相关问答推荐

502 DotNet WebApplication的网关nginx已损坏

DataTables-如何修改来自JSON响应或其他DataTables参数的无结果(ZeroRecords)消息

当前http上下文的http动词

SignalR + Autofac + OWIN:为什么 GlobalHost.ConnectionManager.GetHubContext 不起作用?

ASP.NET Web 应用程序 (.NET Framework) 与 ASP.NET Core Web 应用程序 (.NET Framework) 之间的差异

如何防止 Azure 网站进入Hibernate 状态?

ASP.NET:获取自 1970 年 1 月 1 日以来的毫秒数

返回 JSON 对象 (ASP.NET WebAPI)

OnCheckedChanged 事件未触发

文本框的输入按键触发事件

无法加载文件或程序集'System.Web.WebPages.Razor,版本 = 3.0.0.0

Request.Params 和 Request.Form 什么时候不同?

返回 IHttpActionResult vs IEnumerable vs IQueryable

Web Api 参数始终为空

ASP.NET MVC4 异步控制器 - 为什么要使用?

如何以编程方式将标签的前景色设置为其默认值?

如何在不预编译的情况下使用命令行msbuild部署VS2012网站项目?

如何使用 WebRequest 发布数据并从网页获取响应

哪个事件先调用?母版页 Page_Load 或内容页 Page_Load

MVC4 - 当优化设置为 true 时Bundle 不起作用