Authentication 认证
- Authentication
- Legacy Sign In
- Sign In with Google SDKs
- Industry standards
Google Sign In (Google 登录,过时)
Google Sign-In for Android 已经过时了,现在用 Google Identity Services One Tap sign-in/sign-up
Google Sign-In API (Legacy)
准备工作 配置 Google Console API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // project's top-level build.gradle
allprojects {
repositories {
google()
// If you're using a version of Gradle lower than 4.1, you must instead use:
// maven {
// url 'https://maven.google.com'
// }
}
}
// app-level build.gradle
apply plugin: 'com.android.application'
// ...
dependencies {
implementation 'com.google.android.gms:play-services-auth:20.5.0'
}
|
Sign-in 用的是 Web Client Id
- 用
GoogleSignInOptions
配置必要的信息,请求 id 及用户的基本信息 - 如果需要请求其他 scope 的信息,加上
requestScopes()
(尽可能请求获取所需的最小数据原则),具体可参考 Requesting Additional Scopes - 配置好后,得到一个
GoogleSignInClient
实例
1
2
3
4
| // Check for existing Google Sign In account, if the user is already signed in
// the GoogleSignInAccount will be non-null.
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
updateUI(account);
|
返回了 GoogleSignInAccount 不为 null 说明之前已经登录过了,否则就是没有登录过
如果需要检查用户账号的状态,用下面:
1
2
3
4
5
6
7
8
9
10
11
12
13
| /**
* If you need to detect changes to a user's auth state that happen outside your app, such as access token or ID token revocation, or to perform cross-device sign-in, you might also call GoogleSignInClient.silentSignIn when your app starts.
*/
public static void silentSignIn() {
if (googleApiClient == null) return;
Task<GoogleSignInAccount> googleSignInAccountTask = googleApiClient.silentSignIn();
googleSignInAccountTask.addOnSuccessListener(new OnSuccessListener<GoogleSignInAccount>() {
@Override
public void onSuccess(GoogleSignInAccount account) {
if (account == null) return;
}
});
}
|
1
2
3
4
| <com.google.android.gms.common.SignInButton
android:id="@+id/sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
|
1
2
3
| // Set the dimensions of the sign-in button.
SignInButton signInButton = findViewById(R.id.sign_in_button);
signInButton.setSize(SignInButton.SIZE_STANDARD);
|
1
2
3
4
5
| private void signIn() {
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
// The Task returned from this call is always completed, no need to attach
// a listener.
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
}
}
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
// Signed in successfully, show authenticated UI.
updateUI(account);
} catch (ApiException e) {
// The ApiException status code indicates the detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference for more information.
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
updateUI(null);
}
}
|
- getEmail() 获取 user’s email address
- getId() 获取 user’s Google ID (for client-side use)
- getToken() 获取 ID token
- 将登录获取的信息传递到自己的后端
获取 id token
1
2
3
4
5
6
7
8
| // Request only the user's ID token, which can be used to identify the
// user securely to your backend. This will contain the user's basic
// profile (name, profile picture URL, etc) so you should not need to
// make an additional call to personalize your application.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
.requestEmail()
.build();
|
发送给你的后端服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost("https://yourbackend.example.com/tokensignin");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("idToken", idToken));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
final String responseBody = EntityUtils.toString(response.getEntity());
Log.i(TAG, "Signed in as: " + responseBody);
} catch (ClientProtocolException e) {
Log.e(TAG, "Error sending ID token to backend.", e);
} catch (IOException e) {
Log.e(TAG, "Error sending ID token to backend.", e);
}
|
Sign out user
1
2
3
4
5
6
7
8
9
| private void signOut() {
mGoogleSignInClient.signOut()
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
// ...
}
});
}
|
Disconnect accounts
1
2
3
4
5
6
7
8
9
| private void revokeAccess() {
mGoogleSignInClient.revokeAccess()
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
// ...
}
});
}
|
- requestServerAuthCode 来请求 ServerAuthCode
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Configure sign-in to request offline access to the user's ID, basic
// profile, and Google Drive. The first time you request a code you will
// be able to exchange it for an access token and refresh token, which
// you should store. In subsequent calls, the code will only result in
// an access token. By asking for profile access (through
// DEFAULT_SIGN_IN) you will also get an ID Token as a result of the
// code exchange.
String serverClientId = getString(R.string.server_client_id);
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(new Scope(Scopes.DRIVE_APPFOLDER))
.requestServerAuthCode(serverClientId)
.requestEmail()
.build();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
GoogleSignInAccount account = task.getResult(ApiException.class);
String authCode = account.getServerAuthCode();
// Show signed-un UI
updateUI(account);
// TODO(developer): send code to server and exchange for access/refresh/ID tokens
} catch (ApiException e) {
Log.w(TAG, "Sign-in failed", e);
updateUI(null);
}
|
- 将 ServerAuthCode 发送到你自己的服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| HttpPost httpPost = new HttpPost("https://yourbackend.example.com/authcode");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("authCode", authCode));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
final String responseBody = EntityUtils.toString(response.getEntity());
} catch (ClientProtocolException e) {
Log.e(TAG, "Error sending auth code to backend.", e);
} catch (IOException e) {
Log.e(TAG, "Error sending auth code to backend.", e);
}
|
- 你的后端拿到这个 ServerAuthCode 来交换获取
access and refresh tokens
,用来代表用户来调用 Google APIs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // (Receive authCode via HTTPS POST)
if (request.getHeader("X-Requested-With") == null) {
// Without the `X-Requested-With` header, this request could be forged. Aborts.
}
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.cloud.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://oauth2.googleapis.com/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Auth Code Exchange Demo")
.build();
File file = drive.files().get("appfolder").execute();
// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
|
完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
| public class GoogleSignInHelper {
private static final String TAG = "hacket";
// Web client (auto created by Google Service)
private static final String webClientId = "448384031016-i7ai1nu1426359lag8kd8i0v6p8663o1.apps.googleusercontent.com";
@SuppressLint("StaticFieldLeak")
private static GoogleSignInClient googleApiClient;
/**
* 检查谷歌服务是否可用
*/
public static boolean isGooglePlayServiceEnable(Context context) {
try {
// GooglePlayServicesUtil.isGooglePlayServicesAvailable(this)
int available = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
return available == ConnectionResult.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 登录
*
* @param activity Activity
*/
public static void signInLegacy(Activity activity) {
// Configure sign-in to request the user's ID, email address, and basic profile. ID and
// basic profile are included in DEFAULT_SIGN_IN.
GoogleSignInOptions gso = new GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)//获取用户的基本信息
.requestId() // 明文id
.requestIdToken(webClientId)
// .requestIdToken(getString(R.string.default_web_client_id)) // 从这里获取clientId: https://console.cloud.google.com/apis/credentials?project=the-monkey-king-assistant
.requestEmail()//获取邮箱
.requestProfile()
// .requestScopes()
.build();
// Build a GoogleApiClient with access to GoogleSignIn.API and the options above.
googleApiClient = GoogleSignIn.getClient(activity, gso);//创建 GoogleSignInClient 对象
Intent signInIntent = googleApiClient.getSignInIntent();
activity.startActivityForResult(signInIntent, 10086); // 开始请求授权,请求码自己定
}
/**
* If you need to detect changes to a user's auth state that happen outside your app, such as
* access token or ID token revocation, or to perform cross-device sign-in, you might also call
* GoogleSignInClient.silentSignIn when your app starts.
*/
public static void silentSignIn() {
if (googleApiClient == null) return;
Task<GoogleSignInAccount> googleSignInAccountTask = googleApiClient.silentSignIn();
googleSignInAccountTask.addOnSuccessListener(new OnSuccessListener<GoogleSignInAccount>() {
@Override
public void onSuccess(GoogleSignInAccount account) {
if (account == null) return;
}
});
}
/**
* 登出
*
* @param activity Activity
*/
public static void signOutLegacy(Activity activity) {
if (googleApiClient == null) return;
// Auth.GoogleSignInApi.signOut(googleApiClient) // 过时写法
googleApiClient.signOut()
.addOnCompleteListener(activity, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
boolean successful = task.isSuccessful();
Log.w("hacket", "google signOut successful = " + successful);
}
});
}
public static void revokeAccess(Activity activity) {
if (googleApiClient == null) {
return;
}
// 断开连接--可选
googleApiClient.revokeAccess()
.addOnCompleteListener(activity, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
boolean successful = task.isSuccessful();
Log.w("hacket", "google revokeAccess successful = " + successful);
}
});
}
}
|
UI 展示:
Ref
Google 登录参考这个即可
Google Identity Services (GIS)
是一系列用来 Google 登录和退出新的 API。
New Sign-In API 概述
你不应该用这些 API 在 app launch 或触发加入购物车时提示用户登录,这些场景你应该用 One Tap for Android。
登录过程中会展示这些 UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1; /* unique request id */
private void signIn() {
GetSignInIntentRequest request =
GetSignInIntentRequest.builder()
.setServerClientId(getString(R.string.server_client_id))
.build();
Identity.getSignInClient(activity)
.getSignInIntent(request)
.addOnSuccessListener(
result -> {
try {
startIntentSenderForResult(
result.getIntentSender(),
REQUEST_CODE_GOOGLE_SIGN_IN,
/* fillInIntent= */ null,
/* flagsMask= */ 0,
/* flagsValue= */ 0,
/* extraFlags= */ 0,
/* options= */ null);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Google Sign-in failed");
}
})
.addOnFailureListener(
e -> {
Log.e(TAG, "Google Sign-in failed", e);
});
}
|
需要注意:web_client_id 用的是这个红色框的,而不是 Android client 的那个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) {
try {
SignInCredential credential = Identity.getSignInClient(this).getSignInCredentialFromIntent(data);
// Signed in successfully - show authenticated UI
updateUI(credential);
} catch (ApiException e) {
// The ApiException status code indicates the detailed failure reason.
}
}
}
}
|
也可以用 Activity Result API 来简化 onActivityResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| private val googleLoginLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
val resultCode = result.resultCode
Log.i(TAG, "google login get account info result.resultCode:$resultCode")
if (resultCode == Activity.RESULT_OK) {
try {
val credential =
Identity.getSignInClient(this).getSignInCredentialFromIntent(result.data)
Log.i(TAG, "google login get account info id:${credential.id}")
Log.i(
TAG,
"google login get account info googleIdToken:${credential.googleIdToken}"
)
Log.i(TAG, "google login get account info password:${credential.password}")
Log.i(TAG, "google login get account info givenName:${credential.givenName}")
Log.i(TAG, "google login get account info familyName:${credential.familyName}")
Log.i(
TAG,
"google login get account info displayName:${credential.displayName}"
)
Log.i(
TAG,
"google login get account info profilePictureUri:${credential.profilePictureUri}"
)
updateUI(credential)
} catch (exception: ApiException) {
Log.e(TAG, "google login get account info error :${exception.message}")
exception.printStackTrace()
}
}
}
|
效果图:
完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| public static void signInNewApi(
Activity activity,
ActivityResultLauncher<IntentSenderRequest> launcher) {
// Android client for me.hacket.assistant.samples (auto created by Google Service)
// String clientId = "448384031016-24iv7e5c7ltl7t204urdin6n8l1f7d83.apps.googleusercontent.com";
GetSignInIntentRequest request =
GetSignInIntentRequest.builder()
// .setServerClientId(getString(R.string.server_client_id))
.setServerClientId(webClientId)
.build();
Identity.getSignInClient(activity)
.getSignInIntent(request)
.addOnSuccessListener(
result -> {
launcher.launch(new IntentSenderRequest.Builder(result).build());
// try {
// activity.startIntentSenderForResult(
// result.getIntentSender(),
// REQUEST_CODE_GOOGLE_SIGN_IN,
// /* fillInIntent= */ null,
// /* flagsMask= */ 0,
// /* flagsValue= */ 0,
// /* extraFlags= */ 0,
// /* options= */ null);
// } catch (IntentSender.SendIntentException e) {
// Log.e(TAG, "Google Sign-in failed");
// }
})
.addOnFailureListener(
e -> {
Log.e(TAG, "Google Sign-in failed", e);
});
}
public static void signOutNewApi(Activity activity) {
Identity.getSignInClient(activity)
.signOut()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
Log.i(TAG, "google call logout success");
}
});
}
|
什么是 One tap?
一键登录,检索你之前登录过的账号,避免再次创建账号,减少登录的阻力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| class YourActivity : AppCompatActivity() {
// ...
private lateinit var oneTapClient: SignInClient
private lateinit var signInRequest: BeginSignInRequest
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
oneTapClient = Identity.getSignInClient(this)
signInRequest = BeginSignInRequest.builder()
.setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
.setSupported(true)
.build())
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// Your server's client ID, not your Android client ID.
.setServerClientId(getString(R.string.your_web_client_id))
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(true)
.build())
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(true)
.build()
// ...
}
// ...
}
|
Display the One Tap sign-in UI 展示 One Tap UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| oneTapClient.beginSignIn(signInRequest)
.addOnSuccessListener(this) { result ->
try {
startIntentSenderForResult(
result.pendingIntent.intentSender, REQ_ONE_TAP,
null, 0, 0, 0, null)
} catch (e: IntentSender.SendIntentException) {
Log.e(TAG, "Couldn't start One Tap UI: ${e.localizedMessage}")
}
}
.addOnFailureListener(this) { e ->
// No saved credentials found. Launch the One Tap sign-up flow, or
// do nothing and continue presenting the signed-out UI.
Log.d(TAG, e.localizedMessage)
}
|
Handle the user’s response 处理 One Tap 请求数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| class YourActivity : AppCompatActivity() {
// ...
private val REQ_ONE_TAP = 2 // Can be any integer unique to the Activity
private var showOneTapUI = true
// ...
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_ONE_TAP -> {
try {
val credential = oneTapClient.getSignInCredentialFromIntent(data)
val idToken = credential.googleIdToken
val username = credential.id
val password = credential.password
when {
idToken != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
Log.d(TAG, "Got ID token.")
}
password != null -> {
// Got a saved username and password. Use them to authenticate
// with your backend.
Log.d(TAG, "Got password.")
}
else -> {
// Shouldn't happen.
Log.d(TAG, "No ID token or password!")
}
}
} catch (e: ApiException) {
// ...
}
}
}
}
// ...
}
|
Stop displaying the One Tap UI 停止展示 One Tap UI 框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| class YourActivity : AppCompatActivity() {
// ...
private val REQ_ONE_TAP = 2 // Can be any integer unique to the Activity
private var showOneTapUI = true
// ...
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_ONE_TAP -> {
try {
// ...
} catch (e: ApiException) {
when (e.statusCode) {
CommonStatusCodes.CANCELED -> { // 用户取消
Log.d(TAG, "One-tap dialog was closed.")
// Don't re-prompt the user.
showOneTapUI = false
}
CommonStatusCodes.NETWORK_ERROR -> {
Log.d(TAG, "One-tap encountered a network error.")
// Try again or just ignore.
}
else -> {
Log.d(TAG, "Couldn't get credential from result." +
" (${e.localizedMessage})")
}
}
}
}
}
}
// ...
}
|
Handle sign-out
用户登出了 App,调用 One Tap client’s signOut()
注意:One Tap 需要在之前已经登录过了账号才会弹出 UI;未登录时不会弹出 UI 框,且报错:16: Cannot find a matching credential.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fun savePassword(activity: Activity, username: String, password: String) {
val signInPassword = SignInPassword(username, password)
val savePasswordRequest =
SavePasswordRequest.builder().setSignInPassword(signInPassword).build()
Identity.getCredentialSavingClient(activity)
.savePassword(savePasswordRequest)
.addOnSuccessListener { result ->
activity.startIntentSenderForResult(
result.pendingIntent.intentSender,
REQUEST_CODE_GIS_SAVE_PASSWORD,
/* fillInIntent= */ null,
/* flagsMask= */ 0,
/* flagsValue= */ 0,
/* extraFlags= */ 0,
/* options= */ null
)
}
}
|
UI 效果:
Block Store API 可以让您的应用存储用户凭据,从而可在未来的新设备中取回凭据,并用于重新验证用户。当用户使用一台设备引导另一台设备时,凭据数据就会在设备间传输。
什么是 Smart Lock for Passwords?
程序化的保存和检索凭据,跨端和网站自动登录。
All Smart Lock for Passwords functionality has been migrated to One Tap, and Smart Lock for Passwords is deprecated. Use One Tap instead.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| fun save(
activity: Activity,
name: String,
password: String,
email: String,
requestCode: Int
) {
val credential: Credential = Credential.Builder(email)
.setName(name)
.setPassword(password) // Important: only store passwords in this field.
// Android autofill uses this value to complete
// sign-in forms, so repurposing this field will
// likely cause errors.
.build()
val mCredentialsClient = Credentials.getClient(activity)
mCredentialsClient.save(credential).addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "SAVE: OK")
Toast.makeText(activity, "Credentials saved", Toast.LENGTH_SHORT).show()
return@addOnCompleteListener
}
val e = task.exception
if (e is ResolvableApiException) {
// Try to resolve the save request. This will prompt the user if
// the credential is new.
try {
e.startResolutionForResult(activity, requestCode)
} catch (exception: SendIntentException) {
// Could not resolve the request
Log.e(TAG, "Failed to send resolution.", exception)
Toast.makeText(activity, "Save failed", Toast.LENGTH_SHORT).show()
}
} else {
// Request has no resolution
Toast.makeText(activity, "Save failed", Toast.LENGTH_SHORT).show()
}
}
}
|
如果没有立即 save 成功,就会抛出一个 ResolvableApiException�
异常,调用 startResolutionForResult�()
让用户来确认,效果图如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
| fun retrive(activity: Activity) {
val mCredentialsClient = Credentials.getClient(activity)
val mCredentialRequest = CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE, IdentityProviders.TWITTER)
.build()
mCredentialsClient?.request(mCredentialRequest)
?.addOnCompleteListener(object :
OnCompleteListener<CredentialRequestResponse> {
override fun onComplete(task: Task<CredentialRequestResponse>) {
if (task.isSuccessful) {
// See "Handle successful credential requests"
Log.i(TAG, "onComplete Successful onCredentialRetrieved.")
onCredentialRetrieved(activity, task.result.credential)
return
}
// See "Handle unsuccessful and incomplete credential requests"
val e = task.exception
e?.printStackTrace()
Log.w(TAG, "onComplete Unsuccessful credential request. ${e?.message}")
if (e is ResolvableApiException) {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
resolveResult(activity, e, 0x11)
} else if (e is com.google.android.gms.common.api.ApiException) {
// The user must create an account or sign in manually.
Log.e(TAG, "Unsuccessful credential request.", e)
val ae = e as com.google.android.gms.common.api.ApiException?
val code: Int = ae!!.statusCode
// ...
}
}
})
}
private fun resolveResult(activity: Activity, rae: ResolvableApiException, requestCode: Int) {
try {
rae.startResolutionForResult(activity, requestCode)
// mIsResolving = true
} catch (e: IntentSender.SendIntentException) {
Log.e(TAG, "Failed to send resolution.", e)
e.printStackTrace()
// hideProgress()
}
}
fun onCredentialRetrieved(activity: Activity, credential: Credential?) {
val accountType: String? = credential?.accountType
val name = credential?.name
val id = credential?.id
val password = credential?.password
val idTokens = credential?.idTokens
Log.w(
TAG,
"onCredentialRetrieved accountType: $accountType name: $name id: $id password: $password idTokens: $idTokens"
)
if (accountType == null) {
// Sign the user in with information from the Credential.
Log.i(TAG, "[accountType=null] signInWithPassword id: $id password: $password")
} else if (accountType == IdentityProviders.GOOGLE) {
// The user has previously signed in with Google Sign-In. Silently
// sign in the user with the same ID.
// See https://developers.google.com/identity/sign-in/android/
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
val signInClient = GoogleSignIn.getClient(activity, gso)
val task = signInClient.silentSignIn()
// There's no immediate result ready, displays some progress indicator and waits for the
// async callback.
Log.i(TAG, "[accountType=GOOGLE] silentSignIn.")
task.addOnCompleteListener { t ->
if (t.isSuccessful) {
// There's immediate result available.
val signInAccount = t.result
// ...
} else {
// Unsuccessful sign-in, show the user an error dialog.
// ...
}
}
// ...
}
}
|
1
2
3
4
5
6
7
8
9
10
| mCredentialsClient.delete(credential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
// Credential deletion succeeded.
// ...
}
}
});
|