.NET Core 2.0의 JWT


83

나는 DotNet 코어 2.0에서 JWT를 작동시키기 위해 꽤 모험을 해왔습니다 (현재 최종 릴리스에 도달했습니다). 거기입니다 문서는 있지만, 모든 샘플 코드는 사용되지 않는 API를 사용하고, 긍정적으로 그것을 구현해야하는데 방법을 정확하게 파악하는 현기증의 핵심에 신선한 오는 것 같다. 나는 Jose를 사용해 보았지만 앱. UseJwtBearerAuthentication은 더 이상 사용되지 않으며 다음에 수행 할 작업에 대한 문서가 없습니다.

누구든지 인증 헤더에서 JWT를 구문 분석하고 HS256으로 인코딩 된 JWT 토큰에 대한 요청을 인증 할 수있는 dotnet core 2.0을 사용하는 오픈 소스 프로젝트가 있습니까?

아래 클래스는 예외를 던지지 않지만 승인 된 요청이 없으며 승인되지 않은 이유 수 없습니다 . 응답은 비어있는 401이므로 예외가 없었지만 비밀이 일치하지 않는다는 것을 나타냅니다.

한 가지 이상한 점은 내 토큰이 HS256 알고리즘으로 암호화되어 있지만 어디서나 해당 알고리즘을 사용하도록 지시하는 표시기가 없다는 것입니다.

지금까지 내가 가진 수업은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },


                // Validate the token expiry
                ValidateLifetime = true,
            };

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

나는 [권한 부여] (401)를 사용, 변화없이 모든 요청을 검증 서명 해제
마이클 드레이퍼

2
전체 코드를 게시 할 수 있습니까? 또는 내가 사랑 데모 프로젝트는 당신이 ... 일이있어 어떻게 볼
표트르 Stulinski에게


그쪽으로 당신을 도울 수있는 또 다른 포스트가 있습니다. stackoverflow.com/a/48295906/8417618
Marco Barbero

답변:


87

다음은 컨트롤러를 사용하여 완전히 작동하는 최소 샘플입니다. Postman 또는 JavaScript 호출을 사용하여 확인할 수 있기를 바랍니다.

  1. appsettings.json, appsettings.Development.json. 섹션을 추가하십시오. Key는 다소 길어야하며 Issuer는 서비스의 주소입니다.

    ...
    ,"Tokens": {
        "Key": "Rather_very_long_key",
        "Issuer": "http://localhost:56268/"
    }
    ...
    

    !!! 실제 프로젝트에서는 appsettings.json 파일에 Key를 보관하지 마십시오. 환경 변수에 보관되어야하며 다음과 같이 처리해야합니다.

    Environment.GetEnvironmentVariable("JWT_KEY");
    

업데이트 : .net 코어 설정이 어떻게 작동하는지 확인하면 환경에서 정확히 가져올 필요가 없습니다. 설정을 사용할 수 있습니다. 그러나 대신이 변수를 프로덕션 환경 변수에 쓸 수 있습니다. 그러면 코드가 구성 대신 환경 변수를 선호합니다.

  1. AuthRequest.cs : 로그인 및 비밀번호 전달을위한 값 유지 :

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
    
  2. app.UseMvc () 전에 Configure () 메서드의 Startup.cs :

    app.UseAuthentication();
    
  3. ConfigureServices ()의 Startup.cs :

    services.AddAuthentication()
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
    
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Tokens:Issuer"],
                ValidAudience = Configuration["Tokens:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
            };
    
        });
    
  4. 컨트롤러 추가 :

        [Route("api/[controller]")]
        public class TokenController : Controller
        {
            private readonly IConfiguration _config;
            private readonly IUserManager _userManager;
    
            public TokenController(IConfiguration configuration, IUserManager userManager)
            {
                _config = configuration;
                _userManager = userManager;
            }
    
            [HttpPost("")]
            [AllowAnonymous]
            public IActionResult Login([FromBody] AuthRequest authUserRequest)
            {
                var user = _userManager.FindByEmail(model.UserName);
    
                if (user != null)
                {
                    var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                    if (checkPwd)
                    {
                        var claims = new[]
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                            new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                        };
    
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                        var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                        _config["Tokens:Issuer"],
                        claims,
                        expires: DateTime.Now.AddMinutes(30),
                        signingCredentials: creds);
    
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
    
                return BadRequest("Could not create token");
            }}
    

그게 다야! 건배!

업데이트 : 사람들은 현재 사용자를 얻는 방법을 묻습니다. 할 것:

  1. ConfigureServices ()의 Startup.cs에서 추가

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. 컨트롤러에서 생성자에 추가 :

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
    
  3. 확장을 어딘가에 추가하고 컨트롤러에서 사용합니다 (... 사용).

    public static class IHttpContextAccessorExtension
    {
        public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
            int.TryParse(stringId ?? "0", out int userId);
    
            return userId;
        }
    }
    

1
이것은 저에게 매우 도움이되었습니다. 내가 아직 명확하지 않은 유일한 것은 후속 호출에 대해 토큰을 확인하는 방법 또는 현재 로그인 한 사용자가 누구인지 확인하는 방법입니다.
Travesty3

정말 쉽습니다. 컨트롤러의 메서드 호출에서 : var currentUser = HttpContext.User.Identity.Name; 건배!
alerya

1
야, 정말 큰 도움이 됐어, 자세한 답변을 해주셔서 감사합니다!
Ryanman

1
다음 정보에 감사드립니다. 401 Unauthorized 오류에서 저를 구했습니다. 3. Configure () 메서드의 Startup.cs app.UseMvc () 이전 : app.UseAuthentication ();
Sumia

1
이것은 모순입니다. appsettings 파일에 키를 저장하지 말아야합니다. 아직 여기에서 검색합니다 Configuration [ "Tokens : Key"]). 키를 안전하게 검색하는 다른 서비스가있는 경우이를 ConfigureServices에 어떻게 통합할까요?
War Gravy

18

tokenValidationParameters작품은 다음과 같습니다.

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

또한 다음과 같이 options.RequireHttpsMetadata = false를 추가하십시오.

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

수정 :

전화하는 것을 잊지 마세요

 app.UseAuthentication();

Startup.cs에서 -> 구성 방법 전에 app.UseMvc ();


나는 그것이 app.UseAuthentication (); 트릭을 할 수있는 전화를 걸어야하는데 그게 필요한지 몰랐습니다. 감사합니다!
Michael Draper

ValidAudience, ValidIssuer 및 IssuerSigningKey도 지정해야한다고 생각합니다. 그것은 나를 위해 그것을하지 않고 작동하지 않았다
아드리안 Księżarczyk에게

네, 정확히 그랬습니다. app.UseAuthentication ()을 추가해야했고 그게 전부입니다. 대단히 감사합니다!
Michael Draper

3
+1 app.UseAuthentication();전에 참고 호출되고 app.UseMvc();난 그 하나의 아웃 작업 이일에 대해 지출 - 당신은 토큰이 성공적 권한도없는 당신은 수 (401 개)의 할 경우!
pcdev aug

1
"app.UseAuthentication ();", .net core를 1.0에서 2.0으로 업그레이드 한 후 하루 종일 401 문제를 해결했지만이 게시물을 볼 때까지 해결책을 찾지 못했습니다. 고마워 Adrian.

8

Web Api 데모를 통한 Asp.net Core 2.0 JWT Bearer 토큰 인증 구현

" Microsoft.AspNetCore.Authentication.JwtBearer " 패키지 추가

Startup.cs ConfigureServices ()

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

            });

Startup.cs Configure ()

// ===== Use Authentication ======
        app.UseAuthentication();

User.cs // 예를 들어 모델 클래스입니다. 그것은 무엇이든 될 수 있습니다.

public class User
{
    public Int32 Id { get; set; }
    public string Username { get; set; }
    public string Country { get; set; }
    public string Password { get; set; }
}

UserContext.cs // 그냥 컨텍스트 클래스입니다. 그것은 무엇이든 될 수 있습니다.

public class UserContext : DbContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options)
    {
        this.Database.EnsureCreated();
    }

    public DbSet<User> Users { get; set; }
}

AccountController.cs

[Route("[controller]")]
public class AccountController : Controller
{

    private readonly UserContext _context;

    public AccountController(UserContext context)
    {
        _context = context;
    }

    [AllowAnonymous]
    [Route("api/token")]
    [HttpPost]
    public async Task<IActionResult> Token([FromBody]User user)
    {
        if (!ModelState.IsValid) return BadRequest("Token failed to generate");
        var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
            if (userIdentified == null)
            {
                return Unauthorized();
            }
            user = userIdentified;

        //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
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = DateTime.Now.AddMinutes(30),
            token_type = "bearer"
        });
    }
}

UserController.cs

[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly UserContext _context;

    public UserController(UserContext context)
    {
        _context = context;
        if(_context.Users.Count() == 0 )
        {
            _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
            _context.SaveChanges();
        }
    }

    [HttpGet("[action]")]
    public IEnumerable<User> GetList()
    {
        return _context.Users.ToList();
    }

    [HttpGet("[action]/{id}", Name = "GetUser")]
    public IActionResult GetById(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if(user == null)
        {
            return NotFound();
        }
        return new ObjectResult(user);
    }


    [HttpPost("[action]")]
    public IActionResult Create([FromBody] User user)
    {
        if(user == null)
        {
            return BadRequest();
        }

        _context.Users.Add(user);
        _context.SaveChanges();

        return CreatedAtRoute("GetUser", new { id = user.Id }, user);

    }

    [HttpPut("[action]/{id}")]
    public IActionResult Update(long id, [FromBody] User user)
    {
        if (user == null)
        {
            return BadRequest();
        }

        var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
        if (userIdentified == null)
        {
            return NotFound();
        }

        userIdentified.Country = user.Country;
        userIdentified.Username = user.Username;

        _context.Users.Update(userIdentified);
        _context.SaveChanges();
        return new NoContentResult();
    }


    [HttpDelete("[action]/{id}")]
    public IActionResult Delete(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound();
        }

        _context.Users.Remove(user);
        _context.SaveChanges();

        return new NoContentResult();
    }
}

PostMan에서 테스트 : 응답으로 토큰을 받게됩니다.

다른 웹 서비스의 헤더에 TokenType 및 AccessToken을 전달합니다. 여기에 이미지 설명 입력

행운을 빌어 요! 나는 단지 초보자입니다. asp.net 코어 학습을 시작하는 데 1 주일 밖에 걸리지 않았습니다.


InvalidOperationException : 'AccountController'활성화를 시도하는 동안 'WebApplication8.UserContext'유형에 대한 서비스를 해결할 수 없습니다. 계정 / api / 토큰에 Post에 대한 우편 배달부 호출을 시도 할 때
Kirsten Greed

7

여기에 해결책이 있습니다.

startup.cs에서 먼저 서비스로 구성하십시오.

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

둘째, 구성에서이 서비스를 호출하십시오.

          app.UseAuthentication();

이제 속성을 추가하여 컨트롤러에서 사용할 수 있습니다.

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

angular를 Frond-end로 사용하는 소스 코드에 대한 자세한 내용은 여기를 참조 하십시오.


이것이 내 베이컨을 구한 대답이었습니다! [Authorize]를 사용할 수 있으면 좋을 것 같습니다. 이 Startup.cs withing에 처리 할 수있는 상상
사이먼

1
Simon, 왜냐하면 services.AddAuthentication (). AddCookie (). AddJwtBearer ();와 같은 동일한 asp.net 핵심 mvc 애플리케이션에서 하나 이상의 체계를 가질 수 있기 때문입니다.
Long Field

1
services.AddAuthorization시작 기능 으로 기본 인증 체계를 설정할 수도 있습니다 .
Neville Nazerane 2017 년

@NevilleNazerane의 진술에 대한 후속 조치로 기본 인증 체계 (일반 [Authorize] 데코레이터와 함께 사용됨)를 설정하는 코드가이 질문에 대한 답입니다. services.AddAuthentication (sharedOptions => {sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;})
Ryanman

IssuerSigningKey에 대해이 예제를 따르려고하면 Cannot convert sourcetype 'string'to target type 'Microsoft.IdentityModel.Tokens.SecurityKey'
Kirsten Greed

4

.Net Core 2.0 API에 대한 구현은 다음과 같습니다.

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
            
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json :

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

위의 코드는 모든 컨트롤러에서 인증을 활성화합니다. 익명 액세스를 허용하려면 전체 컨트롤러를 장식 할 수 있습니다.

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

또는 단일 엔드 포인트를 허용하도록 메소드를 장식하십시오.

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

메모:

  • AD 인증에 대한 첫 번째 시도입니다. 잘못된 것이 있으면 알려주세요!

  • Audience클라이언트가 요청한 리소스 ID 와 일치해야합니다 . 우리의 경우 클라이언트 (Angular 웹앱)는 Azure AD에 별도로 등록되었으며 API에서 Audience로 등록한 클라이언트 ID를 사용했습니다.

  • ClientIdAPI에 대한 앱 등록의 응용 프로그램 ID 인 Azure Portal에서 응용 프로그램 ID 라고 합니다 (왜 ??).

  • TenantId라고 디렉토리 ID를 아래에있는 푸른 포털 (왜?)에 푸른 액티브 디렉토리> 속성

  • API를 Azure 호스팅 웹앱으로 배포하는 경우 애플리케이션 설정을 설정해야합니다.

    예. AzureAD : 청중 / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx


3

@alerya의 우수한 답변을 업데이트하기 위해 도우미 클래스를 다음과 같이 수정해야했습니다.

public static class IHttpContextAccessorExtension
    {
        public static string CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {           
            var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
            return userId;
        }
    }

그런 다음 서비스 계층에서 userId를 얻을 수 있습니다. 나는 그것이 컨트롤러에서 쉽다는 것을 알고 있지만, 더 아래로 도전합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.