IT TIP

OWIN 보안-OAuth2 새로 고침 토큰 구현 방법

itqueen 2020. 10. 16. 19:21
반응형

OWIN 보안-OAuth2 새로 고침 토큰 구현 방법


Visual Studio 2013과 함께 제공되는 Web Api 2 템플릿을 사용하고 있으며 사용자 인증 등을 수행하는 일부 OWIN 미들웨어가 있습니다.

에서 OAuthAuthorizationServerOptions발견 i를 OAuth2를 서버 14 일에 만료 토큰 밖으로 손에 설치가 있음

 OAuthOptions = new OAuthAuthorizationServerOptions
 {
      TokenEndpointPath = new PathString("/api/token"),
      Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
      AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
      AllowInsecureHttp = true
 };

이것은 내 최신 프로젝트에 적합하지 않습니다. 사용하여 새로 고칠 수있는 수명이 짧은 bearer_token을 배포하고 싶습니다.refresh_token

인터넷 검색을 많이했는데 도움이되는 것을 찾을 수 없습니다.

그래서 이것은 내가 얼마나 멀리 얻을 수 있었는지입니다. 나는 이제 "WTF do I now"의 지점에 도달했습니다.

클래스 속성에 따라 RefreshTokenProvider구현 하는를 작성했습니다 .IAuthenticationTokenProviderRefreshTokenProviderOAuthAuthorizationServerOptions

    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
       private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            var guid = Guid.NewGuid().ToString();


            _refreshTokens.TryAdd(guid, context.Ticket);

            // hash??
            context.SetToken(guid);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            AuthenticationTicket ticket;

            if (_refreshTokens.TryRemove(context.Token, out ticket))
            {
                context.SetTicket(ticket);
            }
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            throw new NotImplementedException();
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            throw new NotImplementedException();
        }
    }

    // Now in my Startup.Auth.cs
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/api/token"),
        Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
        AllowInsecureHttp = true,
        RefreshTokenProvider = new RefreshTokenProvider() // This is my test
    };

그래서 이제 누군가가를 요청할 때 bearer_token나는 지금을 보내고 refresh_token있습니다.

이제이 refresh_token을 사용하여 새를 가져 오는 방법은 bearer_token무엇입니까? 아마도 특정 HTTP 헤더가 설정된 토큰 끝점에 요청을 보내야 할 것입니다.

입력 할 때 큰 소리로 생각하는 중 ... 내에서 refresh_token 만료를 처리해야합니까 SimpleRefreshTokenProvider? 클라이언트는 어떻게 새로운 것을 얻을 수 refresh_token있습니까?

나는 이것을 잘못하고 싶지 않고 일종의 표준을 따르고 싶기 때문에 독서 자료 / 문서로 정말 할 수 있습니다.


Bearer (다음에서 access_token이라고 함) 및 새로 고침 토큰으로 OWIN 서비스를 구현했습니다. 이에 대한 나의 통찰력은 다른 흐름을 사용할 수 있다는 것입니다. 따라서 access_token 및 refresh_token 만료 시간을 설정하는 방법을 사용하려는 흐름에 따라 다릅니다.

다음에서 두 가지 흐름 AB설명하겠습니다 (원하는 것은 흐름 B라고 제안합니다).

A) access_token 및 refresh_token의 만료 시간은 기본 1200 초 또는 20 분당과 동일합니다. 이 흐름에서는 클라이언트가 먼저 access_token, refresh_token 및 expire_time을 가져 오기 위해 로그인 데이터와 함께 client_id 및 client_secret을 보내야합니다. refresh_token을 사용하면 이제 20 분 동안 새 access_token을 얻을 수 있습니다 (또는 OAuthAuthorizationServerOptions에서 AccessTokenExpireTimeSpan을 설정 한대로). access_token과 refresh_token의 만료 시간이 동일하기 때문에 클라이언트는 만료 시간 전에 새로운 access_token을 받아야합니다! 예를 들어 클라이언트가 본문과 함께 토큰 엔드 포인트에 새로 고침 POST 호출을 보낼 수 있습니다 (참고 : 프로덕션에서 https를 사용해야 함).

grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxx

토큰이 만료되는 것을 방지하기 위해 예를 들어 19 분 후에 새 토큰을 얻습니다.

B) 이 흐름에서 access_token에 대한 단기 만료 및 refresh_token에 대한 장기 만료를 원합니다. 테스트 목적으로 access_token을 10 초 ( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)) 에 만료되도록 설정 하고 refresh_token을 5 분으로 설정했다고 가정 해 보겠습니다 . 이제 refresh_token의 만료 시간을 설정하는 흥미로운 부분이 있습니다. 다음과 같이 SimpleRefreshTokenProvider 클래스의 createAsync 함수에서이 작업을 수행합니다.

var guid = Guid.NewGuid().ToString();


        //copy properties and set the desired lifetime of refresh token
        var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
        {
            IssuedUtc = context.Ticket.Properties.IssuedUtc,
            ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes
            //ExpiresUtc = DateTime.UtcNow.AddMonths(3) 
        };
        /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES 
         *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE 
         *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and 
         *ExpiredUtc to the TICKET*/
        var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);

        //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary<string,AuthenticationTicket>
        // consider storing only the hash of the handle
        RefreshTokens.TryAdd(guid, refreshTokenTicket);            
        context.SetToken(guid);

이제 클라이언트 access_token는 만료 될 때 토큰 엔드 포인트에 refresh_token이 포함 된 POST 호출을 보낼 수 있습니다. 호출의 본문 부분은 다음과 같습니다.grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

한 가지 중요한 점은 CreateAsync 함수뿐만 아니라 Create 함수에서도이 코드를 사용할 수 있다는 것입니다. 따라서 위 코드에 대해 자체 함수 (예 : CreateTokenInternal)를 사용하는 것을 고려해야합니다. 여기에서 refresh_token 흐름을 포함한 다양한 흐름의 구현을 찾을 수 있습니다 (그러나 refresh_token 의 만료 시간은 설정하지 않음).

다음은 github의 IAuthenticationTokenProvider 구현 샘플입니다 (refresh_token의 만료 시간 설정 포함).

OAuth 사양 및 Microsoft API 문서 이외의 자료로 도움을 드릴 수 없어서 죄송합니다. 나는 여기에 링크를 게시 할 것이지만 내 평판은 내가 2 개 이상의 링크를 게시하도록 허용하지 않는다 ....

나는 이것이 access_token 만료 시간과 다른 refresh_token 만료 시간으로 OAuth2.0을 구현하려고 할 때 다른 사람들이 시간을 할애하는 데 도움이되기를 바랍니다. 웹에서 예제 구현을 찾을 수 없었고 (위에 링크 된 thinktecture 중 하나를 제외하고) 저에게 효과가있을 때까지 조사하는 데 몇 시간이 걸렸습니다.

새로운 정보 : 제 경우에는 토큰을받을 수있는 두 가지 다른 가능성이 있습니다. 하나는 유효한 access_token을받는 것입니다. 거기에 다음 데이터로 application / x-www-form-urlencoded 형식의 문자열 본문이있는 POST 호출을 보내야합니다.

client_id=YOURCLIENTID&grant_type=password&username=YOURUSERNAME&password=YOURPASSWORD

두 번째는 access_token이 더 이상 유효하지 않은 경우 application/x-www-form-urlencoded다음 데이터가 포함 된 형식의 문자열 본문과 함께 POST 호출을 전송하여 refresh_token을 시도 할 수 있습니다.grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID


RefreshTokenProvider 를 구현해야합니다 . 먼저 RefreshTokenProvider에 대한 클래스를 만듭니다.

public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
{
    public override void Create(AuthenticationTokenCreateContext context)
    {
        // Expiration time in seconds
        int expire = 5*60;
        context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
        context.SetToken(context.SerializeTicket());
    }

    public override void Receive(AuthenticationTokenReceiveContext context)
    {
        context.DeserializeTicket(context.Token);
    }
}

그런 다음 OAuthOptions 에 인스턴스를 추가합니다 .

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/authenticate"),
    Provider = new ApplicationOAuthProvider(),
    AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire),
    RefreshTokenProvider = new ApplicationRefreshTokenProvider()
};

토큰을 유지하기 위해 배열을 사용해야한다고 생각하지 않습니다. 토큰으로 GUID가 필요하지 않습니다.

context.SerializeTicket ()을 쉽게 사용할 수 있습니다.

아래 코드를 참조하십시오.

public class RefreshTokenProvider : IAuthenticationTokenProvider
{
    public async Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        Create(context);
    }

    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        Receive(context);
    }

    public void Create(AuthenticationTokenCreateContext context)
    {
        object inputs;
        context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs);

        var grantType = ((FormCollection)inputs)?.GetValues("grant_type");

        var grant = grantType.FirstOrDefault();

        if (grant == null || grant.Equals("refresh_token")) return;

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);

        context.SetToken(context.SerializeTicket());
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        context.DeserializeTicket(context.Token);

        if (context.Ticket == null)
        {
            context.Response.StatusCode = 400;
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "invalid token";
            return;
        }

        if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
        {
            context.Response.StatusCode = 401;
            context.Response.ContentType = "application/json";
            context.Response.ReasonPhrase = "unauthorized";
            return;
        }

        context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
        context.SetTicket(context.Ticket);
    }
}

Freddy의 답변 은이 작업을 수행하는 데 많은 도움이되었습니다. 완전성을 위해 토큰 해싱을 구현하는 방법은 다음과 같습니다.

private string ComputeHash(Guid input)
{
    byte[] source = input.ToByteArray();

    var encoder = new SHA256Managed();
    byte[] encoded = encoder.ComputeHash(source);

    return Convert.ToBase64String(encoded);
}

에서 CreateAsync:

var guid = Guid.NewGuid();
...
_refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket);
context.SetToken(guid.ToString());

ReceiveAsync:

public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
    Guid token;

    if (Guid.TryParse(context.Token, out token))
    {
        AuthenticationTicket ticket;

        if (_refreshTokens.TryRemove(ComputeHash(token), out ticket))
        {
            context.SetTicket(ticket);
        }
    }
}

참고 URL : https://stackoverflow.com/questions/20637674/owin-security-how-to-implement-oauth2-refresh-tokens

반응형