developer tip

ASP.NET Core의 토큰 기반 인증 (새로 고침)

optionbox 2020. 11. 15. 11:12
반응형

ASP.NET Core의 토큰 기반 인증 (새로 고침)


ASP.NET Core 애플리케이션으로 작업하고 있습니다. 토큰 기반 인증을 구현하려고하는데 새로운 보안 시스템 을 사용하는 방법을 알 수 없습니다 .

내 시나리오 : 클라이언트가 토큰을 요청합니다. 내 서버는 사용자에게 권한을 부여하고 다음 요청에서 클라이언트가 사용할 access_token을 반환해야합니다.

필요한 것을 정확히 구현하는 방법에 대한 두 가지 훌륭한 기사가 있습니다.

문제는 ASP.NET Core에서 동일한 작업을 수행하는 방법이 명확하지 않다는 것입니다.

내 질문은 : 토큰 기반 인증을 사용하도록 ASP.NET Core Web Api 애플리케이션을 구성하는 방법 입니다. 어떤 방향을 추구해야합니까? 최신 버전에 대한 기사를 작성했거나 어디에서 찾을 수 있는지 알고 있습니까?

감사합니다!


에서 근무 매트 Dekrey의 멋진 대답 , 내가 ASP.NET 코어 (1.0.1)에 대한 작업, 토큰 기반 인증의 완전히 동작하는 예제를 만들었습니다. GitHub ( 1.0.0-rc1 , beta8 , beta7의 대체 분기) 의이 저장소에서 전체 코드 찾을 수 있지만 간단히 말해서 중요한 단계는 다음과 같습니다.

애플리케이션 용 키 생성

이 예에서는 앱이 시작될 때마다 임의의 키를 생성합니다. 키를 생성하여 어딘가에 저장하고 애플리케이션에 제공해야합니다. 무작위 키를 생성하는 방법과 .json 파일에서 가져 오는 방법에 대해서는이 파일을 참조하십시오 . @kspearrin의 의견에서 제안했듯이 데이터 보호 API 는 키를 "올바르게"관리하기위한 이상적인 후보처럼 보이지만 아직 가능한지 확인하지 못했습니다. 문제가 해결되면 풀 리퀘스트를 제출하십시오!

Startup.cs-ConfigureServices

여기에서 토큰에 서명 할 개인 키를로드해야하며, 토큰이 표시 될 때이를 확인하는데도 사용할 것입니다. key아래의 Configure 메서드에서 재사용 할 클래스 수준 변수에 키를 저장하고 있습니다. TokenAuthOptions 는 키를 생성하기 위해 TokenController에 필요한 서명 ID, 대상 및 발급자를 보유하는 간단한 클래스입니다.

// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();

// Create the key, and a set of token options to record signing credentials 
// using that key, along with the other parameters we will need in the 
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
    Audience = TokenAudience,
    Issuer = TokenIssuer,
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};

// Save the token options into an instance so they're accessible to the 
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);

// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
        .RequireAuthenticatedUser().Build());
});

또한 [Authorize("Bearer")]보호하려는 엔드 포인트와 클래스 에서 사용할 수 있도록 권한 부여 정책을 설정했습니다 .

Startup.cs-구성

여기에서 JwtBearerAuthentication을 구성해야합니다.

app.UseJwtBearerAuthentication(new JwtBearerOptions {
    TokenValidationParameters = new TokenValidationParameters {
        IssuerSigningKey = key,
        ValidAudience = tokenOptions.Audience,
        ValidIssuer = tokenOptions.Issuer,

        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,

        // This defines the maximum allowable clock skew - i.e.
        // provides a tolerance on the token expiry time 
        // when validating the lifetime. As we're creating the tokens 
        // locally and validating them on the same machines which 
        // should have synchronised time, this can be set to zero. 
        // Where external tokens are used, some leeway here could be 
        // useful.
        ClockSkew = TimeSpan.FromMinutes(0)
    }
});

TokenController

토큰 컨트롤러에는 Startup.cs에로드 된 키를 사용하여 서명 된 키를 생성하는 메서드가 있어야합니다. Startup에 TokenAuthOptions 인스턴스를 등록 했으므로 TokenController의 생성자에이를 주입해야합니다.

[Route("api/[controller]")]
public class TokenController : Controller
{
    private readonly TokenAuthOptions tokenOptions;

    public TokenController(TokenAuthOptions tokenOptions)
    {
        this.tokenOptions = tokenOptions;
    }
...

그런 다음 로그인 끝점에 대한 처리기에서 토큰을 생성해야합니다. 제 예에서는 사용자 이름과 암호를 가져 와서 if 문을 사용하여 유효성을 검사하지만, 중요한 것은 클레임을 생성하거나로드하는 것입니다. 기반 신원을 확인하고 토큰을 생성합니다.

public class AuthRequest
{
    public string username { get; set; }
    public string password { get; set; }
}

/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
    // Obviously, at this point you need to validate the username and password against whatever system you wish.
    if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
    {
        DateTime? expires = DateTime.UtcNow.AddMinutes(2);
        var token = GetToken(req.username, expires);
        return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
    }
    return new { authenticated = false };
}

private string GetToken(string user, DateTime? expires)
{
    var handler = new JwtSecurityTokenHandler();

    // Here, you should create or look up an identity for the user which is being authenticated.
    // For now, just creating a simple generic identity.
    ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });

    var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
        Issuer = tokenOptions.Issuer,
        Audience = tokenOptions.Audience,
        SigningCredentials = tokenOptions.SigningCredentials,
        Subject = identity,
        Expires = expires
    });
    return handler.WriteToken(securityToken);
}

그리고 그게 다야. [Authorize("Bearer")]보호하려는 메서드 나 클래스에 추가 하기 만하면 토큰이없는 상태에서 액세스하려고하면 오류가 발생합니다. 500 오류 대신 401을 반환하려면 여기 예제에서와 같이 사용자 지정 예외 처리기를 등록해야합니다 .


이것은 내 다른 답변과 실제로 중복 되며 더 많은 관심을 끌수록 더 최신 상태로 유지하는 경향이 있습니다. 댓글도 유용 할 수 있습니다!

.Net Core 2 용으로 업데이트되었습니다.

이 답변의 이전 버전은 RSA를 사용했습니다. 토큰을 생성하는 동일한 코드가 토큰을 확인하는 경우에는 실제로 필요하지 않습니다. 그러나 책임을 분배하는 경우 .NET Framework 인스턴스를 사용하여이 작업을 수행하고 싶을 것입니다 Microsoft.IdentityModel.Tokens.RsaSecurityKey.

  1. 나중에 사용할 몇 가지 상수를 만듭니다. 내가 한 일은 다음과 같습니다.

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
    
  2. 이것을 Startup.cs의 ConfigureServices. 나중에 이러한 설정에 액세스하기 위해 종속성 주입을 사용합니다. 나는 당신 authenticationConfiguration디버그 및 생산을 위해 다른 구성을 가질 수 있는 ConfigurationSection또는 Configuration객체 라고 가정하고 있습니다 . 키를 안전하게 보관하십시오! 모든 문자열이 될 수 있습니다.

    var keySecret = authenticationConfiguration["JwtSigningKey"];
    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret));
    
    services.AddTransient(_ => new JwtSignInHandler(symmetricKey));
    
    services.AddAuthentication(options =>
    {
        // This causes the default authentication scheme to be JWT.
        // Without this, the Authorization header is not checked and
        // you'll get no results. However, this also means that if
        // you're already using cookies in your app, they won't be 
        // checked by default.
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters.ValidateIssuerSigningKey = true;
            options.TokenValidationParameters.IssuerSigningKey = symmetricKey;
            options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
            options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
        });
    

    다른 답변이 다음과 같은 다른 설정을 변경하는 것을 보았습니다 ClockSkew. 기본값은 시계가 정확히 동기화되지 않은 분산 환경에서 작동하도록 설정됩니다. 변경해야하는 유일한 설정입니다.

  3. 인증을 설정합니다. User정보 를 필요로하는 미들웨어 ( 예 : app.UseMvc().

    app.UseAuthentication();
    

    이로 인해 토큰이 SignInManager또는 다른 것과 함께 방출되지는 않습니다 . JWT를 출력하기위한 자체 메커니즘을 제공해야합니다. 아래를 참조하세요.

  4. 을 지정할 수 있습니다 AuthorizationPolicy. 이렇게하면 .NET을 사용하는 인증으로 Bearer 토큰 만 허용하는 컨트롤러 및 작업을 지정할 수 있습니다 [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
    
  5. 여기에 까다로운 부분이 있습니다 : 토큰 구축.

    class JwtSignInHandler
    {
        public const string TokenAudience = "Myself";
        public const string TokenIssuer = "MyProject";
        private readonly SymmetricSecurityKey key;
    
        public JwtSignInHandler(SymmetricSecurityKey symmetricKey)
        {
            this.key = symmetricKey;
        }
    
        public string BuildJwt(ClaimsPrincipal principal)
        {
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(
                issuer: TokenIssuer,
                audience: TokenAudience,
                claims: principal.Claims,
                expires: DateTime.Now.AddMinutes(20),
                signingCredentials: creds
            );
    
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
    

    그런 다음 토큰을 원하는 컨트롤러에서 다음과 같이합니다.

    [HttpPost]
    public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory)
    {
        var principal = new System.Security.Claims.ClaimsPrincipal(new[]
        {
            new System.Security.Claims.ClaimsIdentity(new[]
            {
                new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User")
            })
        });
        return tokenFactory.BuildJwt(principal);
    }
    

    여기서는 이미 교장이 있다고 가정합니다. 당신이 ID를 사용하는 경우 사용할 수 있습니다 IUserClaimsPrincipalFactory<>당신을 변환하는 User으로 ClaimsPrincipal.

  6. 테스트하려면 : 토큰을 가져 와서 jwt.io 의 양식에 넣으십시오 . 위에서 제공 한 지침을 통해 구성의 비밀을 사용하여 서명을 확인할 수도 있습니다!

  7. .Net 4.5의 베어러 전용 인증과 함께 HTML 페이지의 부분보기에서이를 렌더링하는 경우 이제 a ViewComponent사용 하여 동일한 작업을 수행 할 수 있습니다 . 위의 Controller Action 코드와 거의 동일합니다.


설명하는 내용을 달성하려면 OAuth2 / OpenID Connect 인증 서버와 API에 대한 액세스 토큰을 확인하는 미들웨어가 모두 필요합니다. Katana는를 제공하는 데 사용 OAuthAuthorizationServerMiddleware되었지만 ASP.NET Core에는 더 이상 존재하지 않습니다.

앞서 언급 한 자습서에서 사용하는 OAuth2 권한 부여 서버 미들웨어의 실험적 포크 인 AspNet.Security.OpenIdConnect.Server를 살펴볼 것을 제안 합니다. OWIN / Katana 3 버전과 둘 다 지원하는 ASP.NET Core 버전이 있습니다. net451(.NET Desktop) 및 netstandard1.4(.NET Core와 호환).

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server

AspNet.Security.OpenIdConnect.Server를 사용하여 OpenID Connect 권한 부여 서버를 구성하는 방법과 서버 미들웨어에서 발급 한 암호화 된 액세스 토큰의 유효성을 검사하는 방법을 보여주는 MVC Core 샘플을 놓치지 마십시오 : https://github.com/aspnet- contrib / AspNet.Security.OpenIdConnect.Server / blob / dev / samples / Mvc / Mvc.Server / Startup.cs

기본 인증에 해당하는 OAuth2 인 리소스 소유자 비밀번호 부여를 구현하는 방법을 설명하는이 블로그 게시물을 읽을 수도 있습니다. http://kevinchalet.com/2016/07/13/creating-your-own-openid- asos-implementing-the-resource-owner-password-credentials-grant /로 서버 연결

Startup.cs

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication();
    }

    public void Configure(IApplicationBuilder app)
    {
        // Add a new middleware validating the encrypted
        // access tokens issued by the OIDC server.
        app.UseOAuthValidation();

        // Add a new middleware issuing tokens.
        app.UseOpenIdConnectServer(options =>
        {
            options.TokenEndpointPath = "/connect/token";

            // Override OnValidateTokenRequest to skip client authentication.
            options.Provider.OnValidateTokenRequest = context =>
            {
                // Reject the token requests that don't use
                // grant_type=password or grant_type=refresh_token.
                if (!context.Request.IsPasswordGrantType() &&
                    !context.Request.IsRefreshTokenGrantType())
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                        description: "Only grant_type=password and refresh_token " +
                                     "requests are accepted by this 
                    return Task.FromResult(0);
                }

                // Since there's only one application and since it's a public client
                // (i.e a client that cannot keep its credentials private),
                // call Skip() to inform the server the request should be
                // accepted without enforcing client authentication.
                context.Skip();

                return Task.FromResult(0);
            };

            // Override OnHandleTokenRequest to support
            // grant_type=password token requests.
            options.Provider.OnHandleTokenRequest = context =>
            {
                // Only handle grant_type=password token requests and let the
                // OpenID Connect server middleware handle the other grant types.
                if (context.Request.IsPasswordGrantType())
                {
                    // Do your credentials validation here.
                    // Note: you can call Reject() with a message
                    // to indicate that authentication failed.

                    var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
                    identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique id]");

                    // By default, claims are not serialized
                    // in the access and identity tokens.
                    // Use the overload taking a "destinations"
                    // parameter to make sure your claims
                    // are correctly inserted in the appropriate tokens.
                    identity.AddClaim("urn:customclaim", "value",
                        OpenIdConnectConstants.Destinations.AccessToken,
                        OpenIdConnectConstants.Destinations.IdentityToken);

                    var ticket = new AuthenticationTicket(
                        new ClaimsPrincipal(identity),
                        new AuthenticationProperties(),
                        context.Options.AuthenticationScheme);

                    // Call SetScopes with the list of scopes you want to grant
                    // (specify offline_access to issue a refresh token).
                    ticket.SetScopes("profile", "offline_access");

                    context.Validate(ticket);
                }

                return Task.FromResult(0);
            };
        });
    }
}

project.json

{
  "dependencies": {
    "AspNet.Security.OAuth.Validation": "1.0.0",
    "AspNet.Security.OpenIdConnect.Server": "1.0.0"
  }
}

행운을 빕니다!


당신은 사용할 수 있습니다 OpenIddict을 (벌목) 토큰을 제공하기 위해 다음 사용 UseJwtBearerAuthentication하는 API가 / 컨트롤러에 액세스 할 때 그 유효성을 검사 할 수 있습니다.

이것은 기본적으로 필요한 모든 구성입니다 Startup.cs.

ConfigureServices :

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    // this line is added for OpenIddict to plug in
    .AddOpenIddictCore<Application>(config => config.UseEntityFramework());

구성

app.UseOpenIddictCore(builder =>
{
    // here you tell openiddict you're wanting to use jwt tokens
    builder.Options.UseJwtTokens();
    // NOTE: for dev consumption only! for live, this is not encouraged!
    builder.Options.AllowInsecureHttp = true;
    builder.Options.ApplicationCanDisplayErrors = true;
});

// use jwt bearer authentication to validate the tokens
app.UseJwtBearerAuthentication(options =>
{
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.RequireHttpsMetadata = false;
    // must match the resource on your token request
    options.Audience = "http://localhost:58292/";
    options.Authority = "http://localhost:58292/";
});

DbContext가 .NET에서 파생해야하는 것과 같은 다른 사소한 사항이 하나 또는 두 개 있습니다 OpenIddictContext<ApplicationUser, Application, ApplicationRole, string>.

You can see a full length explanation (including the functioning github repo) on this blog post of mine: http://capesean.co.za/blog/asp-net-5-jwt-tokens/


You can have a look at the OpenId connect samples which illustrate how to deal with different authentication mechanisms, including JWT Tokens:

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples

If you look at the Cordova Backend project, the configuration for the API is like so:

app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), 
      branch => {
                branch.UseJwtBearerAuthentication(options => {
                    options.AutomaticAuthenticate = true;
                    options.AutomaticChallenge = true;
                    options.RequireHttpsMetadata = false;
                    options.Audience = "localhost:54540";
                    options.Authority = "localhost:54540";
                });
    });

The logic in /Providers/AuthorizationProvider.cs and the RessourceController of that project are also worth having a look at ;).

Moreover, I have implemented a single page application with token based authentication implementation using the Aurelia front end framework and ASP.NET core. There is also a signal R persistent connection. However I have not done any DB implementation. Code can be seen here: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

Hope this helps,

Best,

Alex

참고URL : https://stackoverflow.com/questions/30546542/token-based-authentication-in-asp-net-core-refreshed

반응형