SDK 中的令牌访问、重用和自动刷新
Token access, reuse and auto refresh within the SDK
来自 C# 和其他语言,刚接触 F# 并尝试移植我用 OO 语言构建的 SDK 库。 SDK 负责首先检索访问令牌,在静态字段上设置,然后设置特定时间间隔以在令牌到期前不断刷新令牌。
令牌在 Authentication
class 上设置为静态字段,并且每次到期前都会更新。
然后 SDK 中的其他参与者联系 Authentication
class,读取它的 static
字段令牌,在调用 REST 端点之前放入他们的 Authorization headers
。 SDK 中的所有参与者在每次调用中始终重复使用相同的令牌,直到它过期并自动获取更新的令牌。
这就是行为,我仍在努力思考几个概念,但我相信边做边学。
这个 F# 库将从 C# 调用,它将首先传递凭据,然后实例化其他 classes/actors 并在传递参数时调用它们的方法每个单独的方法。那些其他参与者将使用这个存储的令牌。
要点 基本上在 Authentication
中有两个静态字段,并允许在刷新其中一个静态字段(即 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
}
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
我的 F# 尝试
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# 测试
[<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";
问题
在上面的单元测试中
Authentication.credentials.clientId is empty string
Assert fails
调用令牌服务后,我无法在单元测试中访问静态成员。我处理这一切的方式有问题。
我需要借助一些 F# 代码将 C# 行为转换为 F#。我已经构建了身份验证 class 并且在实现中遇到了一些问题,尤其是在静态成员和随后访问它们方面。此外,我想遵循函数式编程的规则,并了解它是如何在 F# 的 Functional World 中完成的。请帮助我在 F# 代码中翻译此行为。
这个问题的惯用函数方法是首先尝试摆脱全局状态。
有几种方法可以解决这个问题,但我认为最好的方法是提供一个 AuthenticationContext
,其中包含您的 C# 代码在全局状态下保存的数据,并使每个调用 migth 更新凭据,return 其结果 连同可能更新的授权上下文 .
基本上,给定一个使用令牌进行 API 调用的方法
type MakeApiCall<'Result> = Token -> 'Result
我们想创建这样的东西:
type AuthenticatedCall<'Result> = AuthenticationContext -> 'Result * AuthenticationContext
你还可以让上下文跟踪它是否需要更新(例如,通过存储上次更新的时间戳、存储到期日期或其他内容),并提供两个功能
type NeedsRenewal = AuthenticationContext -> bool
type Renew = AuthenticationContext -> AuthenticationContext
现在,如果您使用函数
获取凭据
type GetAccessToken = AuthenticationContext -> Token * AuthenticationContext
您可以让该方法的实施首先检查凭据是否需要更新,如果需要,则在 returning 之前更新它们。
因此,示例实现可能如下所示:
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'
现在,如果您每次进行 API 调用时都可以访问上一次调用的 AuthenticationContext
,则基础架构会负责为您更新令牌。
您很快就会注意到,这只会将问题推向跟踪身份验证上下文,并且您将不得不多次传递它。例如,如果你想进行两次连续的 API 调用,你将执行以下操作:
let context = getInitialContext ()
let resultA, context' = makeFirstCall context
let resultB, context'' = makeSecondCall context'
如果我们可以构建一些可以为我们跟踪上下文的东西,这样我们就不必传递它,那不是很好吗?
来自 C# 和其他语言,刚接触 F# 并尝试移植我用 OO 语言构建的 SDK 库。 SDK 负责首先检索访问令牌,在静态字段上设置,然后设置特定时间间隔以在令牌到期前不断刷新令牌。
令牌在 Authentication
class 上设置为静态字段,并且每次到期前都会更新。
然后 SDK 中的其他参与者联系 Authentication
class,读取它的 static
字段令牌,在调用 REST 端点之前放入他们的 Authorization headers
。 SDK 中的所有参与者在每次调用中始终重复使用相同的令牌,直到它过期并自动获取更新的令牌。
这就是行为,我仍在努力思考几个概念,但我相信边做边学。
这个 F# 库将从 C# 调用,它将首先传递凭据,然后实例化其他 classes/actors 并在传递参数时调用它们的方法每个单独的方法。那些其他参与者将使用这个存储的令牌。
要点 基本上在 Authentication
中有两个静态字段,并允许在刷新其中一个静态字段(即 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
}
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
我的 F# 尝试
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# 测试
[<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";
问题
在上面的单元测试中
Authentication.credentials.clientId is empty string
Assert fails
调用令牌服务后,我无法在单元测试中访问静态成员。我处理这一切的方式有问题。
我需要借助一些 F# 代码将 C# 行为转换为 F#。我已经构建了身份验证 class 并且在实现中遇到了一些问题,尤其是在静态成员和随后访问它们方面。此外,我想遵循函数式编程的规则,并了解它是如何在 F# 的 Functional World 中完成的。请帮助我在 F# 代码中翻译此行为。
这个问题的惯用函数方法是首先尝试摆脱全局状态。
有几种方法可以解决这个问题,但我认为最好的方法是提供一个 AuthenticationContext
,其中包含您的 C# 代码在全局状态下保存的数据,并使每个调用 migth 更新凭据,return 其结果 连同可能更新的授权上下文 .
基本上,给定一个使用令牌进行 API 调用的方法
type MakeApiCall<'Result> = Token -> 'Result
我们想创建这样的东西:
type AuthenticatedCall<'Result> = AuthenticationContext -> 'Result * AuthenticationContext
你还可以让上下文跟踪它是否需要更新(例如,通过存储上次更新的时间戳、存储到期日期或其他内容),并提供两个功能
type NeedsRenewal = AuthenticationContext -> bool
type Renew = AuthenticationContext -> AuthenticationContext
现在,如果您使用函数
获取凭据type GetAccessToken = AuthenticationContext -> Token * AuthenticationContext
您可以让该方法的实施首先检查凭据是否需要更新,如果需要,则在 returning 之前更新它们。
因此,示例实现可能如下所示:
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'
现在,如果您每次进行 API 调用时都可以访问上一次调用的 AuthenticationContext
,则基础架构会负责为您更新令牌。
您很快就会注意到,这只会将问题推向跟踪身份验证上下文,并且您将不得不多次传递它。例如,如果你想进行两次连续的 API 调用,你将执行以下操作:
let context = getInitialContext ()
let resultA, context' = makeFirstCall context
let resultB, context'' = makeSecondCall context'
如果我们可以构建一些可以为我们跟踪上下文的东西,这样我们就不必传递它,那不是很好吗?