Pertanyaan ASP.NET Core 2.0 Bearer Auth tanpa Identity


Saya pikir saya memiliki tujuan yang sangat sederhana dalam pikiran ketika saya berangkat sehari yang lalu untuk mengimplementasikan web browser autentikasi autent di .NET core 2.0, tetapi saya belum mendapatkan apa pun yang bekerja dari jarak jauh. Berikut daftar yang saya coba lakukan:

  • Menerapkan tolak beruang yang dilindungi token
  • Menerbitkan token & menyegarkan token dari titik akhir dalam proyek yang sama
  • Gunakan atribut [Otorisasi] untuk mengontrol akses ke permukaan api
  • Tidak menggunakan ASP.Net Identity (Saya memiliki berat pengguna / keanggotaan yang jauh lebih ringan)

Saya benar-benar baik-baik saja dengan membangun identitas / klaim / prinsip dalam login dan menambahkannya untuk meminta konteks, tetapi saya belum melihat satu contoh pun tentang cara mengeluarkan dan mengkonsumsi token autentif / refresh dalam webapi Core 2.0 tanpa Identitas. Saya telah melihat contoh 1.x MSDN cookie tanpa Identitas, tetapi itu tidak membuat saya cukup memahami untuk memenuhi persyaratan di atas.

Saya merasa ini mungkin skenario umum dan seharusnya tidak sesulit ini (mungkin tidak, mungkin hanya kurangnya dokumentasi / contoh?). Sejauh yang saya tahu, IdentityServer4 tidak kompatibel dengan Core 2.0 Auth, opendiddict tampaknya membutuhkan Identity. Saya juga tidak ingin meng-host token endpoint dalam proses terpisah, tetapi dalam contoh webap yang sama.

Adakah yang bisa mengarahkan saya ke contoh konkret, atau setidaknya memberikan beberapa petunjuk tentang langkah / pilihan terbaik apa?


17
2017-08-16 13:48


asal


Jawaban:


Melakukan pengeditan untuk membuatnya kompatibel dengan ASP.NET Core 2.0.


Pertama, beberapa paket Nuget:

  • Microsoft.AspNetCore.Authentication.JwtBearer
  • Microsoft.AspNetCore.Identity
  • System.IdentityModel.Tokens.Jwt
  • System.Security.Cryptography.Csp

Kemudian beberapa objek transfer data dasar.

// 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; }
}

Masuk ke fungsi yang tepat, Anda perlu metode web login / token untuk benar-benar mengirim token otorisasi kepada pengguna.

[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";
    }
}

Anda mungkin memperhatikan penciptaan token masih "ajaib" dilewatkan oleh beberapa ITokenProvider imajiner. Tentukan antarmuka penyedia token.

public interface ITokenProvider
{
    string CreateToken(User user, DateTime expiry);

    // TokenValidationParameters is from Microsoft.IdentityModel.Tokens
    TokenValidationParameters GetValidationParameters();
}

Saya mengimplementasikan pembuatan token dengan kunci keamanan RSA pada JWT. Begitu...

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.
        };
    }
}

Jadi Anda sekarang menghasilkan token. Saatnya untuk benar-benar memvalidasi mereka dan mentransfernya. Pergi ke Startup.cs Anda.

Di 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();
});

Kemudian Configure()

public void Configure(IApplicationBuilder app)
{
    app.UseAuthentication();

    // Whatever else you're putting in here...

    app.UseMvc();
}

Itu harus tentang semua yang Anda butuhkan. Semoga saya tidak melewatkan apa pun.

Hasilnya bahagia ...

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

16
2017-08-21 23:47



Berikut pada jawaban @Mitch: Tumpukan Auth berubah sedikit pindah ke .NET Core 2.0. Jawaban di bawah ini hanya menggunakan implementasi baru.

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();
        }
    }
}

Token Controller

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"
            });
        }
    }
}

Pengontrol Nilai

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" };
        }
    }
}

Semoga ini membantu!


12
2017-09-19 13:13