OWIN सिक्योरिटी - OAuth2 रीफ्रेश टोकन कैसे लागू करें


80

मैं वेब एपि 2 टेम्प्लेट का उपयोग कर रहा हूं जो विजुअल स्टूडियो 2013 के साथ आता है जिसमें उपयोगकर्ता प्रमाणीकरण और पसंद करने के लिए कुछ ओडिन मिडलवेयर है।

में OAuthAuthorizationServerOptionsमैंने देखा 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
 };

यह मेरी नवीनतम परियोजना के लिए उपयुक्त नहीं है। मैं कम जीवित भालू_कोकन्स को सौंपना चाहूंगा जिन्हें एक का उपयोग करके ताज़ा किया जा सकता हैrefresh_token

मैंने बहुत सारे काम किए हैं और कुछ भी मददगार नहीं हो सकता।

तो यह है कि मैं कितनी दूर पाने में कामयाब रहा। मैं अब "डब्ल्यूटीएफ क्या मैं अब" के बिंदु पर पहुंच गया हूं।

मैंने क्लास पर प्रॉपर्टी के अनुसार एक 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, जो बहुत अच्छा है।

तो अब मैं एक नया पाने के लिए इस रिफ्रेश_टोकन का उपयोग कैसे करूं bearer_token, संभवतः मुझे कुछ विशिष्ट HTTP हेडर सेट के साथ अपने टोकन समापन बिंदु पर एक अनुरोध भेजने की आवश्यकता है?

जैसे ही मैं जोर से सोचता हूं ... क्या मुझे अपने में रिफ्रेश_टोकन की समाप्ति को संभालना चाहिए SimpleRefreshTokenProvider? एक ग्राहक एक नया कैसे प्राप्त करेगा refresh_token?

मैं वास्तव में कुछ पठन सामग्री / प्रलेखन के साथ कर सकता था क्योंकि मैं यह गलत नहीं करना चाहता और किसी प्रकार के मानक का पालन करना चाहूंगा।


7
Owin और OAuth का उपयोग करके ताज़ा टोकन लागू करने के लिए एक महान ट्यूटोरियल है: bitoftech.net/2014/07/16/…
फिलिप बर्गस्ट्रॉम

जवाबों:


76

बस बियरर के साथ मेरी OWIN सेवा को लागू किया (जिसे निम्नलिखित में access_token कहा जाता है) और ताज़ा करें टोकन। इसमें मेरी अंतर्दृष्टि यह है कि आप विभिन्न प्रवाह का उपयोग कर सकते हैं। तो यह उस प्रवाह पर निर्भर करता है जिसका आप उपयोग करना चाहते हैं कि आप अपने access_token और ताज़ा_token समाप्ति समय कैसे सेट करते हैं।

मैं follwing में दो प्रवाह A और B का वर्णन करूँगा (मेरा सुझाव है कि आपके पास जो प्रवाह B है वह है):

ए) एक्सेस_टोकन और रिफ्रेश_टोकन का समय समाप्त हो रहा है क्योंकि यह डिफ़ॉल्ट रूप से 1200 सेकंड या 20 मिनट के लिए है। इस फ्लो को एक्सेस_टोकन, रिफ्रेश_टोकन और एक्सपायर_टाइम पाने के लिए लॉगइन डेटा के साथ क्लाइंट_एड और क्लाइंट_सेक्रट भेजने के लिए सबसे पहले आपके क्लाइंट की जरूरत होती है। रिफ्रेश_टोकन के साथ अब 20 मिनट के लिए एक नया एक्सेस_टोकन प्राप्त करना संभव है (या जो भी आप AccessTokenExpireTimeSpan को OAuthAuthorizationServerOptions में सेट करते हैं)। इस कारण से कि access_token और ताज़ा_token का समय समाप्ति समय एक ही है, आपका ग्राहक समाप्ति समय से पहले एक नया access_token प्राप्त करने के लिए ज़िम्मेदार है! उदाहरण के लिए, आपका ग्राहक शरीर के साथ अपने टोकन समापन बिंदु पर एक ताज़ा पोस्ट कॉल भेज सकता है (टिप्पणी: आपको उत्पादन में https का उपयोग करना चाहिए)

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

समाप्ति से टोकन को रोकने के लिए उदाहरण के लिए 19 मिनट के बाद एक नया टोकन प्राप्त करना।

बी) इस प्रवाह में आप अपने access_token के लिए एक छोटी अवधि की समाप्ति और अपने ताज़ा करने के लिए एक लंबी अवधि की समाप्ति चाहते हैं। परीक्षण के उद्देश्य के लिए मान लें कि आपने access_token को 10 सेकंड में समाप्त करने के लिए ( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)) और रिफ्रेश_टोकन को 5 मिनट में सेट किया है। अब यह ताज़ा समय के समाप्ति समय को सेट करने के दिलचस्प हिस्से की बात आती है: आप इस तरह से 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समय समाप्त होने पर अपने टोकन समाप्ति बिंदु पर एक ताज़ा_टोकन के साथ एक पोस्ट कॉल भेजने में सक्षम है। कॉल का मुख्य भाग इस तरह दिख सकता है:grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

एक महत्वपूर्ण बात यह है कि आप इस कोड का उपयोग न केवल अपने CreateAsync फ़ंक्शन में, बल्कि अपने Create फ़ंक्शन में भी कर सकते हैं। तो आपको उपरोक्त कोड के लिए अपने स्वयं के फ़ंक्शन (जैसे क्रिएटटोकइनइंटरनेट कहा जाता है) का उपयोग करने पर विचार करना चाहिए। यहां आप रिफ्रेश_टोकन फ्लो (लेकिन रिफ्रेश_टोकन की समाप्ति समय निर्धारित किए बिना) सहित विभिन्न प्रवाह के कार्यान्वयन पा सकते हैं

यहाँ एक नमूना कार्यान्वयन है IAuthenticationTokenProvider पर github (रिफ्रेश का समय समाप्त करने के साथ)

मुझे खेद है कि मैं OAuth चश्मा और Microsoft API दस्तावेज़ीकरण से आगे की सामग्री के साथ मदद नहीं कर सकता। मैं यहाँ लिंक पोस्ट करूँगा, लेकिन मेरी प्रतिष्ठा मुझे 2 से अधिक लिंक पोस्ट नहीं करने देगी ...।

मुझे आशा है कि यह OAuth2.0 को ताज़ा करने के लिए OAuth2.0 को लागू करने की कोशिश करते समय खाली समय के लिए कुछ अन्य लोगों की मदद कर सकता है। मुझे वेब पर एक उदाहरण कार्यान्वयन नहीं मिल रहा है (ऊपर उल्लिखित एक विचारधारा को छोड़कर) और जब तक यह मेरे लिए काम नहीं करता तब तक मुझे कुछ घंटों की जांच में लगा।

नई जानकारी: मेरे मामले में मुझे टोकन प्राप्त करने की दो अलग-अलग संभावनाएं हैं। एक वैध पहुँच_को प्राप्त करना है। वहाँ मुझे निम्नलिखित डेटा के साथ प्रारूप एप्लिकेशन / x-www-form-urlencoded में स्ट्रिंग बॉडी के साथ एक POST कॉल भेजना है

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

दूसरा यह है कि अगर access_token मान्य नहीं है, तो हम application/x-www-form-urlencodedनिम्नलिखित डेटा के साथ एक स्ट्रिंग बॉडी के साथ POST कॉल भेजकर रिफ्रेश_टोकन को आज़मा सकते हैंgrant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID


1
आपकी एक टिप्पणी कहती है "विचार को संभाल के रखना है", क्या यह टिप्पणी ऊपर की रेखा पर लागू नहीं होनी चाहिए? टिकट मूल गाइड रखता है, लेकिन हम केवल गाइड के हैश को स्टोर करते हैं RefreshTokens, इसलिए यदि RefreshTokensलीक हुआ है, तो एक हमलावर इस जानकारी का उपयोग कर सकता है !?
निबंध

ऐसा लगता है; OA से पूछा: github.com/thinktecture/Thinktecture.IdentityModel/commit/…
Esskar

1
जैसा कि प्रवाह B में वर्णित है आप Access_token के लिए समाप्ति समय तक पहुँच सकते हैं AccessTokenExpireTimeSpan = TimeSpan.FromMinutes (60) का उपयोग करके एक घंटे के लिए या उस समय के लिए जिससे आप access_token को समाप्त करना चाहते हैं। लेकिन इस बात का ध्यान रखें कि यदि आप अपने प्रवाह में रीफ्रेश_टोकन का उपयोग कर रहे हैं, तो आपके रिफ्रेश_टोकन की समाप्ति अवधि आपके एक्सेस_टोकन में से एक से अधिक होनी चाहिए। इसलिए उदाहरण के लिए हमें access_token के लिए 24 घंटे और रिफ्रेश_टोकन के लिए 2 महीने का समय चाहिए। आप OAuth कॉन्फ़िगरेशन में access_token की समाप्ति समय निर्धारित कर सकते हैं।
फ्रेडी

12
अपने टोकन के लिए गिड्स का उपयोग न करें और न ही उनमें से कोई भी राख, यह सुरक्षित नहीं है। एक यादृच्छिक बाइट सरणी उत्पन्न करने और उसे एक स्ट्रिंग में बदलने के लिए System.Cryptography नाम स्थान का उपयोग करें। अन्यथा आपके ताज़ा टोकन का अनुमान बल के हमलों से लगाया जा सकता है।
बॉन

1
@ क्या आप एक दिशा-निर्देश का अनुमान लगाने वाले हैं? हमलावर द्वारा अनुरोध किए जाने से पहले ही आपके दर सीमक को रास्ते में लात मार देनी चाहिए। और यदि नहीं, तो यह अभी भी एक गाइड है।
लोनिक्स

46

आपको 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()
};

यह हर बार एक नया ताज़ा टोकन बनाएगा और लौटाएगा, यहाँ तक कि आप केवल एक नया एक्सेस टोकन वापस करने में इंट्रेसस्टेड हो सकते हैं और एक नया ताज़ा टोकन नहीं दे सकते। उदाहरण के लिए, एक एक्सेस टोकन के लिए कॉलिंग लेकिन एक ताज़ा टोकन के साथ और क्रेडेंशियल (उपयोगकर्ता नाम / पासवर्ड) नहीं। क्या इससे बचने का कोई तरीका है?
मैटिस

आप कर सकते हैं, लेकिन यह सुंदर नहीं है। context.OwinContext.Environmentएक होता है Microsoft.Owin.Form#collectionकुंजी जो आपको एक देता है FormCollectionजहाँ आप अनुदान प्रकार खोजने के लिए और तदनुसार एक टोकन जोड़ सकते हैं। यह कार्यान्वयन को लीक कर रहा है, यह भविष्य के अपडेट के साथ किसी भी बिंदु पर टूट सकता है, और अगर यह OWIN मेजबानों के बीच पोर्टेबल है तो मैं अनिश्चित हूं।
12

3
आप OwinRequest ऑब्जेक्ट से "अनुदान_प्रकार" मान को पढ़कर हर बार एक नया ताज़ा टोकन जारी करने से बच सकते हैं, जैसे कि: var form = await context.Request.ReadFormAsync(); var grantType = form.GetValue("grant_type"); फिर ताज़ा टोकन जारी करें यदि अनुदान प्रकार "ताज़ा_तक" नहीं है
Duy

1
@mattias आप अभी भी उस परिदृश्य में एक नया ताज़ा टोकन वापस करना चाहते हैं। अन्यथा क्लाइंट को पहली बार रिफ्रेश करने के बाद एक लर्च में छोड़ दिया जाता है, क्योंकि दूसरा एक्सेस टोकन समाप्त हो जाता है और उनके पास फिर से क्रेडेंशियल के लिए प्रॉम्प्ट किए बिना रिफ्रेश करने का कोई तरीका नहीं है।
एरिक एस्किल्डसन

9

मुझे नहीं लगता कि टोकन बनाए रखने के लिए आपको एक सरणी का उपयोग करना चाहिए। न ही आपको टोकन के रूप में एक गाइड की आवश्यकता है।

आप आसानी से संदर्भ का उपयोग कर सकते हैं।

मेरा नीचे कोड देखें।

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

2

फ्रेडी के जवाब से मुझे यह काम पाने में बहुत मदद मिली। पूर्णता के लिए यहां बताया गया है कि आप टोकन के हैशिंग को कैसे लागू कर सकते हैं:

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

हैशिंग इस मामले में कैसे मदद करता है?
अजाक्स

3
@ अजाक्स: मूल समाधान ने गाइड को संग्रहीत किया। हैशिंग के साथ हम प्लेन-टेक्स्ट टोकन नहीं बल्कि उसके हैश रख रहे हैं। यदि आप उदाहरण के लिए डेटाबेस में टोकन स्टोर करते हैं, तो हैश को स्टोर करना बेहतर है। यदि डेटाबेस से छेड़छाड़ की जाती है, तो टोकन अनुपयोगी होते हैं जब तक कि वे एन्क्रिप्ट नहीं किए जाते हैं।
कैलिस

न केवल बाहरी खतरों से रक्षा करने के लिए, बल्कि कर्मचारियों (जिनके पास डेटाबेस तक पहुंच है) को टोकन चोरी करने से रोकने के लिए भी।
लोनिक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.