Xác thực là quá trình xác định danh tính của một người, thường được gọi là đăng ký hoặc đăng nhập của người dùng. Uỷ quyền là quá trình cấp hoặc từ chối quyền truy cập vào dữ liệu hoặc tài nguyên. Ví dụ: ứng dụng của bạn yêu cầu người dùng đồng ý cho phép truy cập vào Google Drive của họ.
Các lệnh gọi xác thực và uỷ quyền phải là hai quy trình riêng biệt và riêng biệt dựa trên nhu cầu của ứng dụng.
Nếu ứng dụng của bạn có các tính năng có thể sử dụng dữ liệu API của Google nhưng không bắt buộc phải có trong các tính năng cốt lõi của ứng dụng, hãy thiết kế ứng dụng sao cho có thể xử lý linh hoạt các trường hợp không thể truy cập vào dữ liệu API. Ví dụ: bạn có thể ẩn danh sách các tệp đã lưu gần đây khi người dùng chưa cấp quyền truy cập vào Drive.
Bạn chỉ nên yêu cầu quyền truy cập vào các phạm vi cần thiết để truy cập vào API của Google khi người dùng thực hiện một hành động cần có quyền truy cập vào một API cụ thể. Ví dụ: bạn nên yêu cầu quyền truy cập vào Drive của người dùng bất cứ khi nào người dùng nhấn vào nút Lưu vào Drive.
Bằng cách tách riêng quy trình uỷ quyền khỏi quy trình xác thực, bạn có thể tránh làm người dùng mới bị quá tải hoặc khiến người dùng bối rối về lý do họ được yêu cầu cấp một số quyền nhất định.
Để xác thực, bạn nên sử dụng Credential Manager API. Để cho phép các hành động cần quyền truy cập vào dữ liệu người dùng do Google lưu trữ, bạn nên sử dụng AuthorizationClient.
Thiết lập dự án trên Google Cloud Console
- Mở dự án của bạn trong Cloud Console hoặc tạo một dự án nếu bạn chưa có.
- Trên trang Thương hiệu, hãy đảm bảo rằng tất cả thông tin đều đầy đủ và chính xác.
- Đảm bảo ứng dụng của bạn có Tên ứng dụng, Biểu trưng ứng dụng và Trang chủ ứng dụng chính xác. Những giá trị này sẽ hiển thị cho người dùng trên màn hình xin phép của tính năng Đăng nhập bằng Google khi họ đăng ký và trên màn hình Ứng dụng và dịch vụ bên thứ ba.
- Đảm bảo bạn đã chỉ định URL của chính sách quyền riêng tư và điều khoản dịch vụ của ứng dụng.
- Trên trang Ứng dụng khách, hãy tạo mã ứng dụng khách Android cho ứng dụng của bạn nếu bạn chưa có. Bạn sẽ cần chỉ định tên gói và chữ ký SHA-1 của ứng dụng.
- Chuyển đến trang Ứng dụng.
- Nhấp vào Tạo ứng dụng.
- Chọn loại ứng dụng Android.
- Nhập tên cho ứng dụng OAuth. Tên này xuất hiện trên trang Ứng dụng của dự án để xác định ứng dụng.
- Nhập tên gói của ứng dụng Android. Giá trị này được xác định trong thuộc tính
packagecủa phần tử<manifest>trong tệpAndroidManifest.xml. - Nhập dấu vân tay chứng chỉ ký SHA-1 của App Distribution.
- Nếu ứng dụng của bạn sử dụng tính năng ký ứng dụng của Google Play, hãy sao chép vân tay số SHA-1 từ trang ký ứng dụng của Play Console.
- Nếu bạn quản lý kho khoá và khoá ký của riêng mình, hãy sử dụng tiện ích keytool đi kèm với Java để in thông tin chứng chỉ ở định dạng con người có thể đọc được. Sao chép giá trị
SHA-1trong phầnCertificate fingerprintscủa kết quả keytool. Hãy xem phần Xác thực ứng dụng trong tài liệu về API Google cho Android để biết thêm thông tin. - (Không bắt buộc) Xác minh quyền sở hữu ứng dụng Android của bạn.
- Trên trang Ứng dụng khách, hãy tạo một mã ứng dụng khách "Ứng dụng web" mới nếu bạn chưa có. Bạn có thể
bỏ qua các trường "Authorized JavaScript Origins" (Nguồn gốc JavaScript được uỷ quyền) và "Authorized redirect URIs" (URI chuyển hướng được uỷ quyền)
vào lúc này. Mã ứng dụng khách này sẽ được dùng để xác định máy chủ phụ trợ của bạn khi máy chủ này giao tiếp với các dịch vụ xác thực của Google.
- Chuyển đến trang Ứng dụng.
- Nhấp vào Tạo ứng dụng.
- Chọn loại Ứng dụng web.
Xác minh quyền sở hữu ứng dụng
Bạn có thể xác minh quyền sở hữu ứng dụng để giảm nguy cơ ứng dụng bị mạo danh.
Để hoàn tất quy trình xác minh, bạn có thể sử dụng Tài khoản nhà phát triển trên Google Play (nếu có) và ứng dụng của bạn đã được đăng ký trên Google Play Console. Bạn phải đáp ứng các yêu cầu sau để xác minh thành công:
- Bạn phải có một ứng dụng đã đăng ký trong Google Play Console có cùng tên gói và dấu vân tay chứng chỉ ký SHA-1 với ứng dụng OAuth Android mà bạn đang hoàn tất quy trình xác minh.
- Bạn phải có quyền Quản trị đối với ứng dụng trong Google Play Console. Tìm hiểu thêm về việc quản lý quyền truy cập trong Google Play Console.
Trong phần Xác minh quyền sở hữu ứng dụng của ứng dụng Android, hãy nhấp vào nút Xác minh quyền sở hữu để hoàn tất quy trình xác minh.
Nếu xác minh thành công, một thông báo sẽ xuất hiện để xác nhận rằng quy trình xác minh đã thành công. Nếu không, lời nhắc lỗi sẽ xuất hiện.
Để khắc phục lỗi xác minh, hãy thử làm như sau:
- Đảm bảo ứng dụng bạn đang xác minh là ứng dụng đã đăng ký trong Google Play Console.
- Đảm bảo rằng bạn có quyền Quản trị đối với ứng dụng trong Google Play Console.
Khai báo phần phụ thuộc
Trong tệp build.gradle của mô-đun, hãy khai báo các phần phụ thuộc bằng cách sử dụng phiên bản mới nhất của thư viện Dịch vụ nhận dạng của Google.
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:22.0.0"
}
Yêu cầu cấp quyền cần thiết cho hành động của người dùng
Bất cứ khi nào người dùng thực hiện một hành động cần có thêm phạm vi, hãy gọi AuthorizationClient.authorize(). Ví dụ: nếu người dùng thực hiện một hành động yêu cầu quyền truy cập vào bộ nhớ ứng dụng Drive, hãy làm như sau:
Kotlin
val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequestBuilder.build())
.addOnSuccessListener { authorizationResult ->
if (authorizationResult.hasResolution()) {
val pendingIntent = authorizationResult.pendingIntent
// Access needs to be granted by the user
startAuthorizationIntent.launch(IntentSenderRequest.Builder(pendingIntent!!.intentSender).build())
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
if (authorizationResult.hasResolution()) {
// Access needs to be granted by the user
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
Khi xác định ActivityResultLauncher, hãy xử lý phản hồi như trong đoạn mã sau, giả sử việc này được thực hiện trong một mảnh. Mã này kiểm tra xem các quyền cần thiết đã được cấp thành công hay chưa, sau đó thực hiện hành động của người dùng.
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
// extract the result
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
// extract the result
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (ApiException e) {
// log exception
}
});
}
Nếu bạn đang truy cập vào API Google ở phía máy chủ, hãy gọi phương thức getServerAuthCode() từ AuthorizationResult để lấy mã uỷ quyền mà bạn gửi đến phần phụ trợ để đổi lấy mã truy cập và mã làm mới. Để tìm hiểu thêm, hãy xem phần Duy trì quyền truy cập liên tục vào dữ liệu của người dùng.
Thu hồi quyền truy cập vào dữ liệu hoặc tài nguyên của người dùng
Để thu hồi quyền truy cập đã cấp trước đó, hãy gọi AuthorizationClient.revokeAccess(). Ví dụ: nếu người dùng đang xoá tài khoản của họ khỏi ứng dụng của bạn và ứng dụng của bạn trước đó đã được cấp quyền truy cập vào DriveScopes.DRIVE_FILE, hãy sử dụng mã sau để thu hồi quyền truy cập:
Kotlin
val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
.addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));
Xoá bộ nhớ đệm mã thông báo
Mã truy cập OAuth được lưu vào bộ nhớ đệm cục bộ khi nhận được từ máy chủ, giúp tăng tốc độ truy cập và giảm số lượng lệnh gọi mạng. Các mã thông báo này sẽ tự động bị xoá khỏi bộ nhớ đệm khi hết hạn, nhưng cũng có thể không hợp lệ vì những lý do khác.
Nếu bạn nhận được IllegalStateException khi sử dụng mã thông báo, hãy xoá bộ nhớ đệm cục bộ để đảm bảo rằng yêu cầu uỷ quyền tiếp theo cho mã truy cập sẽ được gửi đến máy chủ OAuth. Đoạn mã sau đây xoá invalidAccessToken khỏi bộ nhớ đệm cục bộ:
Kotlin
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
.addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }
Java
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));
Lấy thông tin người dùng trong quá trình uỷ quyền
Phản hồi uỷ quyền không chứa bất kỳ thông tin nào về tài khoản người dùng đã được sử dụng; phản hồi chỉ chứa mã thông báo cho các phạm vi được yêu cầu. Ví dụ: phản hồi để lấy mã truy cập nhằm truy cập vào Google Drive của người dùng không tiết lộ danh tính của tài khoản mà người dùng đã chọn, mặc dù mã này có thể được dùng để truy cập vào các tệp trên Drive của người dùng. Để lấy thông tin như tên hoặc email của người dùng, bạn có các lựa chọn sau:
Đăng nhập cho người dùng bằng Tài khoản Google của họ thông qua Credential Manager API trước khi yêu cầu uỷ quyền. Phản hồi xác thực từ Trình quản lý thông tin xác thực bao gồm thông tin người dùng như địa chỉ email, đồng thời đặt tài khoản mặc định của ứng dụng thành tài khoản đã chọn; nếu cần, bạn có thể theo dõi tài khoản này trong ứng dụng. Yêu cầu uỷ quyền tiếp theo sẽ sử dụng tài khoản này làm tài khoản mặc định và bỏ qua bước chọn tài khoản trong quy trình uỷ quyền. Để sử dụng một tài khoản khác cho việc uỷ quyền, hãy xem phần Uỷ quyền từ một tài khoản không phải tài khoản mặc định.
Trong yêu cầu uỷ quyền, ngoài các phạm vi bạn muốn (ví dụ:
Drive scope), hãy yêu cầu các phạm viuserinfo,profilevàopenid. Sau khi nhận được mã truy cập, hãy lấy thông tin người dùng bằng cách gửi một yêu cầu HTTPGETđến điểm cuối thông tin người dùng OAuth (https://www.googleapis.com/oauth2/v3/userinfo) bằng thư viện HTTP mà bạn muốn và thêm mã truy cập mà bạn đã nhận được trong tiêu đề, tương đương với lệnhcurlsau:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"Phản hồi là
UserInfo, giới hạn trong các phạm vi được yêu cầu, được định dạng bằng JSON.
Uỷ quyền từ một tài khoản không phải tài khoản mặc định
Nếu bạn sử dụng Trình quản lý thông tin xác thực để xác thực và chạy AuthorizationClient.authorize(), thì tài khoản mặc định của ứng dụng sẽ được đặt thành tài khoản mà người dùng chọn. Điều này có nghĩa là mọi lệnh gọi uỷ quyền tiếp theo đều sử dụng tài khoản mặc định này. Để buộc hiển thị bộ chọn tài khoản, hãy đăng xuất người dùng khỏi ứng dụng bằng API clearCredentialState() trong Trình quản lý thông tin xác thực.
Duy trì quyền truy cập liên tục vào dữ liệu của người dùng
Nếu bạn cần truy cập vào dữ liệu của người dùng từ ứng dụng, hãy gọi AuthorizationClient.authorize() một lần; trong các phiên tiếp theo và miễn là người dùng không xoá các quyền đã cấp, hãy gọi cùng một phương thức để lấy mã truy cập nhằm đạt được mục tiêu của bạn mà không cần người dùng tương tác. Mặt khác, nếu cần truy cập vào dữ liệu của người dùng ở chế độ ngoại tuyến từ máy chủ phụ trợ, thì bạn cần yêu cầu một loại mã thông báo khác có tên là "mã làm mới".
Mã truy cập được thiết kế có chủ đích để có thời gian tồn tại ngắn và có thời gian tồn tại là một giờ. Nếu mã truy cập bị chặn hoặc bị xâm phạm, thì thời hạn hiệu lực có giới hạn của mã này sẽ giảm thiểu khả năng bị sử dụng sai mục đích. Sau khi hết hạn, mã thông báo sẽ không còn hiệu lực và mọi nỗ lực sử dụng mã thông báo đó sẽ bị máy chủ tài nguyên từ chối. Vì mã truy cập có thời hạn ngắn, nên máy chủ sử dụng mã làm mới để duy trì quyền truy cập liên tục vào dữ liệu của người dùng. Mã làm mới là mã thông báo có thời gian hoạt động dài, được ứng dụng dùng để yêu cầu mã truy cập có thời gian hoạt động ngắn từ máy chủ uỷ quyền khi mã truy cập cũ hết hạn mà không cần người dùng tương tác.
Để nhận mã làm mới, trước tiên, bạn cần lấy mã xác thực (hoặc mã uỷ quyền) trong bước uỷ quyền trong ứng dụng bằng cách yêu cầu "quyền truy cập khi không có mạng", sau đó đổi mã xác thực lấy mã làm mới trên máy chủ của bạn. Bạn cần lưu trữ mã làm mới dài hạn một cách an toàn trên máy chủ của mình vì mã này có thể được dùng nhiều lần để lấy mã truy cập mới. Do đó, bạn không nên lưu trữ mã làm mới trên thiết bị vì lý do bảo mật. Thay vào đó, bạn nên lưu trữ các thông tin này trong máy chủ phụ trợ của ứng dụng, nơi diễn ra quá trình trao đổi mã truy cập.
Sau khi mã xác thực được gửi đến máy chủ phụ trợ của ứng dụng, bạn có thể đổi mã đó lấy mã truy cập ngắn hạn trên máy chủ và mã làm mới dài hạn bằng cách làm theo các bước trong hướng dẫn uỷ quyền tài khoản. Quá trình trao đổi này chỉ nên diễn ra ở phần phụ trợ của ứng dụng.
Kotlin
// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener { authorizationResult ->
startAuthorizationIntent.launch(IntentSenderRequest.Builder(
pendingIntent!!.intentSender
).build())
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build();
Identity.getAuthorizationClient(getContext())
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));
Đoạn mã sau đây giả định rằng quy trình uỷ quyền được bắt đầu từ một mảnh.
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// short-lived access token
accessToken = authorizationResult.accessToken
// store the authorization code used for getting a refresh token safely to your app's backend server
val authCode: String = authorizationResult.serverAuthCode
storeAuthCodeSafely(authCode)
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// short-lived access token
accessToken = authorizationResult.getAccessToken();
// store the authorization code used for getting a refresh token safely to your app's backend server
String authCode = authorizationResult.getServerAuthCode()
storeAuthCodeSafely(authCode);
} catch (ApiException e) {
// log exception
}
});
}