- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我一直在使用 FirebaseAuth 登录用户,并注意到 FirebaseUser 的 getToken 方法返回的 ID token 与 FCM FirebaseInstanceIdService 不同。
FirebaseAuth ID token 和 FCM 实例 ID onTokenRefresh 返回的 token 之间到底有什么区别? Instance ID token 和 ID token 的命名相似,因此让我感到有些困惑。
根据我的观察,通过 FirebaseUser 对象上的 getIdToken
方法获得的 FirebaseAuth token 将在一小时后过期。
getIdToken(boolean forceRefresh)
例如 token 是...
01-18 17:20:08.904 15947-15947/? D/FragmentCreate: Token found without force refresh from a single thread eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJodHR....JTXpA
我观察到将 forceRefresh 设置为 true 会立即更改 token 。
例如。
01-18 17:22:16.990 15947-15947/? D/FragmentCreate: Token found single thread after force refresh eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod.......xQCA
现在,当我在最后一次显示的强制刷新一小时后调用 getIdToken(false) 时, token 确实发生了变化,因为它已经过期,并且通过深入研究 firebase 代码,我可以看到一些像 isValid() 这样的检查,我我猜测会检查 token 过期并刷新它,即使强制刷新为假也是如此。
为了让事情变得有趣,我在 token 过期后不久从两个线程同时调用了 getIdToken
(false) 来查看两者是否打印不同的 token ,因为两者都会看到 token 在同时,因此两者都应该尝试刷新 token
例如。
01-18 18:25:29.479 29849-29849/com.foodiniq.waitlist.katana D/FragmentCreate: Token found from thread1 after expiry eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod........GpdbA
01-18 18:25:30.071 29849-29849/com.foodiniq.waitlist.katana D/FragmentCreate: Token found from thread1 after expiry eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod........GpdbA
令我惊讶的是, token 确实如我所料刷新了,但它们都给出了相同的结果。这种暗示用于在内部获取 token 的方法可能是同步的,并且至少在从两个不同线程将 force refresh 设置为 false 调用时会返回相同的结果。对于 forceRefresh 设置为 true 也显示了类似的结果,同时线程打印具有相同值的新 token
我假设 getIdToken(false) 方法是线程安全的,并且在同时调用时在所有线程中总是只返回相同的值,这是否正确?此行为是否与 getIdToken(true) 不同?
P.S 用于在不从两个线程刷新的情况下获取 token 的代码是
Thread testThread1 = new Thread(new Runnable() {
@Override
public void run() {
if(FirebaseAuth.getInstance().getCurrentUser()!=null){
FirebaseAuth.getInstance().getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
Log.d("FragmentCreate","Token found from thread1 after expiry "+task.getResult().getToken());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("FragmentCreate","Token failed from main thread single "+e.toString());
}
});
}
}
});
Thread testThread2 = new Thread(new Runnable() {
@Override
public void run() {
if(FirebaseAuth.getInstance().getCurrentUser()!=null){
FirebaseAuth.getInstance().getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
Log.d("FragmentCreate","Token found from thread2 after expiry "+task.getResult().getToken());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("FragmentCreate","Token failed from main thread single "+e.toString());
}
});
}
}
});
testThread1.start();
testThread2.start();
单线程强制刷新调用token的方法是:
if(FirebaseAuth.getInstance().getCurrentUser()!=null){
FirebaseAuth.getInstance().getCurrentUser().getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
Log.d("FragmentCreate","Token found single thread after force refresh "+task.getResult().getToken());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("FragmentCreate","Token failed from main thread single "+e.toString());
}
});
}
由于以下原因,我想要一个线程安全的实现:
每次启动应用程序时,我都会在不强制刷新的情况下获取 token 。并在所有后续请求中使用此 token 。(因此可能存在此 token 可能已过期的时间点,但用户保持应用程序运行一个小时的情况不太可能)
正如您所说,我在后端所做的就是验证 id token 。这里可能存在 token 可能已过期的点。所以我发送了一个响应代码,告诉客户端手动刷新 token 。现在这是在后端的所有 Servlet 上完成的,因此许多 Servlet 可以同时返回过期的响应代码。在这个响应中,我从任何获得响应代码的线程刷新客户端上的 token ,从而导致许多不同的线程调用客户端上的刷新方法。我基本上担心这可能是 token 刷新的配额
最佳答案
Firebase ID token 和 FCM token 是两个完全不同的东西:第一个是身份验证 token ,需要在您的后端服务器上验证请求,后者是唯一标识应用程序安装的实例,知道发送给谁正确的信息。还要考虑 Firebase 用户身份验证 ID - 它是线程安全的,并且是特定用户帐户的唯一 ID。
来自官方doc
Verify ID Tokens
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.
同时 ( from official doc ):
Access the device registration token
On initial startup of your app, the FCM SDK generates a registration token for the client app instance. If you want to target single devices or create device groups, you'll need to access this token by extending FirebaseInstanceIdService.This section describes how to retrieve the token and how to monitor changes to the token. Because the token could be rotated after initial startup, you are strongly recommended to retrieve the latest updated registration token.
The registration token may change when:
- The app deletes Instance ID
- The app is restored on a new device
- The user uninstalls/reinstall the app
- The user clears app data.
因此,重点似乎有点偏离:为什么要拥有线程安全且唯一的身份验证 token ?在您的后端服务器上,您可以通过调用适当的函数(例如 Node.js 代码)简单地获取用户的 uid
:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// ...
}).catch(function(error) {
// Handle error
});
如果这些信息改变了对代码中发生的事情的理解,请随时详细说明/改进问题。
关于android - 什么是 FirebaseAuth ID token 及其线程安全的 getIdToken 方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48322473/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!