SDK中的令牌访问,重用和自动刷新 [英] Token access, reuse and auto refresh within the SDK

查看:77
本文介绍了SDK中的令牌访问,重用和自动刷新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

来自C#和其他语言,并且是F#的新功能,并尝试移植以OO语言构建的SDK库。 SDK负责先获取访问令牌,然后在静态字段上进行设置,然后设置特定的时间间隔以在令牌到期之前连续刷新令牌。



令牌在 Authentication 类上设置为静态字段,并且每次到期前都会更新。 / p>

然后,SDK中的其他参与者接触到 Authentication 类,将其读取为静态字段令牌,在调用REST端点之前放在其授权标头中。 SDK中的所有参与者在每次调用之前都会重复使用相同的令牌,直到令牌到期为止,并且会自动获取一个新的令牌。



这就是行为,我仍在尝试包装我的脑袋围绕几个概念,但我相信边做边学。



将从C#调用此F#库,并在其中传递凭据,然后实例化其他类/角色并调用它们方法,同时为每个方法传递参数。其他参与者是将使用此存储令牌的参与者。



要点在<$ c $中基本上有两个静态字段c>身份验证,并在刷新静态字段之一即令牌的同时启用对其他参与者的访问。

 公共类认证
{
公共静态令牌令牌;
个公共静态凭证凭证;
公共静态令牌RequestToken(凭据凭据)
{
Authentication.credentials =凭据//缓存以备后用
// http REST调用以基于凭据/ api密钥访问令牌
// Authentication.token = someAccessTokenObject; //缓存结果

}

公共静态令牌AddTokenObserver(凭据凭据)
{
this.RequestToken(credentials);
//设置间隔,例如每24小时调用一次RequestToken
}
}

公共类Class1
{
public someReturnObject RequestService1(someParams } {
//访问Authentication.credentials
//访问Authentication.token
//放入授权标头
//并调用Web服务
}
// +重复相同模式的其他几种方法
}

公共类Class2
{
public someReturnObject RequestService2(someParams){
//访问Authentication.credentials
//访问Authentication.token
//放入授权标头
//并调用Web服务
}
// +重复相同模式的其他几种方法
}

使用SDK strong>

  //通过传递crede初始化SDK ntials并启用自动刷新
Authentication.AddTokenObserver(someCredentials)//设置令牌观察者

Class1 c1 = new Class1();
c1.RequestService1(someObject1); //使用凭证&来自身份验证的令牌

c2类= new Class2();
c2.RequestService2(someObject2); //使用凭证&来自身份验证的令牌






我的F#尝试

  type Credentials = {mutable clientId:string;可变的clientSecret:字符串;} 
类型的令牌= {mutable access_token:字符串;可变的refresh_token:字符串}

类型身份验证=
静态成员令牌= {access_token =; refresh_token =};
静态成员凭证= {clientId =; clientSecret =;}
new()= {}

成员this.RequestToken(credentials)=
let data:byte [] = System.Text.Encoding.ASCII .GetBytes();

let host = https://example.com;
let url = sprintf%s& client_id =%s& client_secret =%s主机凭据。clientId凭据.clientSecret

let request = WebRequest.Create(url):?> HttpWebRequest
request.Method<- POST
request.ContentType<- application / x-www-form-urlencoded
request.Accept<- application / json; charset = UTF-8
request.ContentLength<--(int64)data.Length

use requestStream = request.GetRequestStream()
requestStream.Write(data,0, (data.Length))
requestStream.Flush()
requestStream.Close()

let response = request.GetResponse():?> HttpWebResponse

use reader = new StreamReader(response.GetResponseStream())
let output = reader.ReadToEnd()

printf%A response.StatusCode / /如果response.StatusCode = HttpStatusCode.OK引发错误

Authentication.credentials.clientId<-凭据.clientId

让t = JsonConvert.DeserializeObject< Token>(输出)
Authentication.token.access_token<-t.access_token
Authentication.token.token_type<-t.token_type

reader.Close()
响应。 Close()
请求。Abort()

F#测试

  [< TestMethod>] 
成员this.TestCredentials()=
让凭据= {
clientId =某些客户ID;
clientSecret =某些客户机密;
}
let authenticaiton = new Authentication()

试试
authenticaiton.RequestToken(credentials)
printfn%s,%s凭据。clientId Authentication.credentials.clientId // Authentication.credentials.clientId为空字符串
Assert.IsTrue(credentials.clientId = Authentication.credentials.clientId)//失败

| :? WebException-> printfn错误;

问题



在上述单元测试中

  Authentication.credentials.clientId为空字符串
声明失败

调用令牌服务后,我无法访问单元测试中的静态成员。我如何一起解决所有问题。



我需要一些F#代码的帮助来翻译F#中的C#行为。我已经建立了Authentication类,并且在实现中遇到了一些问题,尤其是在静态成员周围以及随后对其进行访问方面。另外,我想遵循函数式编程的规则,并学习如何在F#中的函数式世界中完成它。请帮我用F#代码转换此行为。

解决方案

解决此问题的惯用方法是,首先尝试获取



有几种解决方法,但是我认为最有效的一种方法是提供 AuthenticationContext ,其中包含您的C#代码保持全局状态的数据,并进行每个调用,使 migth 续订凭据,并返回其结果以及可能更新的授权上下文



基本上,给出了使用令牌进行API调用的方法

  type MakeApiCall<'Result> =令牌-> '结果

我们要创建类似这样的内容:

 类型AuthenticatedCall<'Result> = AuthenticationContext-> '结果* AuthenticationContext 

然后,您还可以让上下文跟踪是否需要更新(例如通过存储最后一次更新的时间戳,存储到期日期或其他),并提供两个功能

  type NeedsRenewal = AuthenticationContext-> bool 
类型Renew = AuthenticationContext-> AuthenticationContext

现在,如果您获得具有功能的凭据

  type GetAccessToken = AuthenticationContext->令牌* AuthenticationContext 

您可以通过检查凭据是否需要续签来开始实施该方法,以及



因此,示例实现可能如下所示:

  type AuthenticationContext = {
凭据:凭证
令牌:令牌
expiryDate:DateTimeOffset
}

let needsRenewal context =
context.expiryDate> DateTimeOffset.UtcNow.AddMinutes(-5)//添加一些安全裕量

让续订上下文=
让令牌= getNewToken上下文。凭证
让expiryDate = DateTimeOffset.UtcNow.AddDays (1)
{具有令牌的上下文=令牌,expiryDate = expiryDate}

let getAccessToken context =
let context'=
如果需要更新上下文
然后更新上下文
else上下文

返回上下文'.token,上下文'

让makeAuthenticatedCall上下文makeApicall =
让令牌,上下文'= getAccessToken上下文

让结果= makeApiCall令牌

结果,上下文'

现在,如果每次进行API调用时都可以从上一次调用访问 AuthenticationContext ,则基础结构将为您续签令牌。 / p>




您会很快注意到,这只会使问题继续跟踪f身份验证上下文,您将不得不大量传递它。例如,如果要进行两个连续的API调用,则将执行以下操作:

  let context = getInitialContext() 

let resultA,context'= makeFirstCall context
let resultB,context''= makeSecondCall context'

如果我们可以构建一些可以为我们跟踪上下文的东西,这不是很好吗,那么我们就不必传递它了吗?



事实证明,有一种针对这种情况的功能模式


Coming from C# and other languages, and new to F# and trying to port an SDK Library that I built in an OO language. The SDK is responsible for retrieving access token first, setting on a static field and then setting a specific interval to continuosly refresh the token before it's expiry.

The token is set as static field on the Authentication class and is updated every time before it reaches expiry.

Then other actors within the SDK reach out to Authentication class, read it's static field token, place in their Authorization headers before calling the REST endpoints. All actors within the SDK reuse the same token throughout for each call until it's expired and a newer one is auto fetched.

That's the behavior, I'm still trying to wrap my head around several concepts but I believe in learning while doing it.

This F# library will be called from C# where it will be passed Credentials to begin with and then subsequently instantiating other classes/actors and calling their methods while passing params for each individual method. Those other actors are the one who will be using this stored token.

The gist is basically having two static fields within the Authentication and enable access to other actors while refreshing the one of the static fields i.e. Token.

public class Authentication 
{
     public static Token token;
     public static Credentials credentials;
     public static Token RequestToken(Credentials credentials)
     {
           Authentication.credentials = credentials // cache for subsequent use
           // http REST call to access token based on credentials/api keys etc.
           // Authentication.token = someAccessTokenObject; // cache result

     }

     public static Token AddTokenObserver(Credentials credentials) 
     {
            this.RequestToken(credentials);
            // set interval, like call RequestToken every 24 hrs
     }
}

public class Class1 
{
     public someReturnObject RequestService1(someParams) {
           // accesses Authentication.credentials
           // accesses Authentication.token
           // places in the authorization headers 
           // and calls the web service
     }
      // + several other methods that repeats the same pattern
}

public class Class2
{
     public someReturnObject RequestService2(someParams) {
           // accesses Authentication.credentials
           // accesses Authentication.token
           // places in the authorization headers 
           // and calls the web service
     }
     // + several other methods that repeats the same pattern
}

Use of SDK

// initialize SDK by passing credentials and enable auto refresh
Authentication.AddTokenObserver(someCredentials) // set token observer

Class1 c1 = new Class1();
c1.RequestService1(someObject1); // uses credentials & token from Authentication

Class c2 = new Class2();
c2.RequestService2(someObject2); // uses credentials & token from Authentication


My F# Attempt

type Credentials = {mutable clientId: string; mutable clientSecret: string;}
type Token = {mutable access_token: string; mutable refresh_token: string}

type Authentication =
    static member token = {access_token = ""; refresh_token = ""};
    static member credentials = {clientId = ""; clientSecret = "";}            
    new() = {}

    member this.RequestToken(credentials) =
        let data : byte[] = System.Text.Encoding.ASCII.GetBytes("");

        let host = "https://example.com";
        let url = sprintf "%s&client_id=%s&client_secret=%s" host credentials.clientId credentials.clientSecret

        let request = WebRequest.Create(url) :?> HttpWebRequest
        request.Method <- "POST"
        request.ContentType <- "application/x-www-form-urlencoded"
        request.Accept <- "application/json;charset=UTF-8"
        request.ContentLength <- (int64)data.Length

        use requestStream = request.GetRequestStream() 
        requestStream.Write(data, 0, (data.Length))
        requestStream.Flush()
        requestStream.Close()

        let response = request.GetResponse() :?> HttpWebResponse

        use reader = new StreamReader(response.GetResponseStream())
        let output = reader.ReadToEnd()

        printf "%A" response.StatusCode // if response.StatusCode = HttpStatusCode.OK throws an error

        Authentication.credentials.clientId <- credentials.clientId

        let t = JsonConvert.DeserializeObject<Token>(output)                            
        Authentication.token.access_token <- t.access_token
        Authentication.token.token_type <- t.token_type

        reader.Close()
        response.Close()
        request.Abort()

F# Test

    [<TestMethod>]    
    member this.TestCredentials() = 
        let credentials = {
            clientId = "some client id"; 
            clientSecret = "some client secret"; 
        }
        let authenticaiton = new Authentication()

        try
            authenticaiton.RequestToken(credentials) 
            printfn "%s, %s" credentials.clientId Authentication.credentials.clientId // Authentication.credentials.clientId is empty string
            Assert.IsTrue(credentials.clientId = Authentication.credentials.clientId) // fails
        with
            | :? WebException -> printfn "error";

Question

In the above unit test

Authentication.credentials.clientId is empty string
Assert fails

I couldn't access static members within my unit tests after I called the token service. There's something wrong with how I'm approaching this all together.

I need help in translating C# behavior in F# with the help of some F# code. I've built the Authentication class and have some problems in the implementation especially around static members and subsequently accessing them. Also I want to follow the rules of functional programming and learn how it's done in Functional World in F#. Please help me translate this behavior in F# code.

解决方案

The idiomatic functional approach to this problem, would be to first try to get rid of the global state.

There are several approaches to this, but the one that I think will work best would be to provide an AuthenticationContext which has the data your C# code keeps in global state, and make each call that migth renew the credentials, return its result together with a potentially updated authorization context.

Basically, given a method to make an API call with a token

type MakeApiCall<'Result> = Token -> 'Result

we want to create something like this:

type AuthenticatedCall<'Result> = AuthenticationContext -> 'Result * AuthenticationContext

You can then also let the context keep track of whether it needs renewal (e.g. by storing the timestamp when it was last renewed, storing the expiry date, or something else), and provide two functions

type NeedsRenewal = AuthenticationContext -> bool
type Renew = AuthenticationContext -> AuthenticationContext

Now, if you obtain the credentials with a function

type GetAccessToken = AuthenticationContext -> Token * AuthenticationContext

you can let the implementation of that method start by checking if the credentials need renewal, and if so renew them before returning.

So, a sample implementation might look like this:

type AuthenticationContext = {
    credentials : Credentials
    token : Token
    expiryDate : DateTimeOffset
}

let needsRenewal context =
    context.expiryDate > DateTimeOffset.UtcNow.AddMinutes(-5) // add some safety margin

let renew context =
    let token = getNewToken context.Credentials
    let expiryDate = DateTimeOffset.UtcNow.AddDays(1)
    { context with token = token, expiryDate = expiryDate }

let getAccessToken context =
    let context' =
        if needsRenewal context
        then renew context
        else context

    return context'.token, context'

let makeAuthenticatedCall context makeApicall =
    let token, context' = getAccessToken context

    let result = makeApiCall token

    result, context'

Now, if every time you make an API call you have access to the AuthenticationContext from the previous call, the infrastructure will take care of renewing the token for you.


You'll notice quickly that this will just push the problem to keeping track of the authentication context, and that you will have to pass that around a lot. For example, if you want to make two consecutive API calls, you'll do the following:

let context = getInitialContext ()

let resultA, context' = makeFirstCall context
let resultB, context'' = makeSecondCall context'

Wouldn't it be nice if we could build something that would keep track of the context for us, so we didn't have to pass it around?

It turns out there's a functional pattern for this situation.

这篇关于SDK中的令牌访问,重用和自动刷新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆