Xác thực giúp xác định danh tính của một người và 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 ứng dụng truy cập vào Google Drive của người dùng.
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à khác biệt dựa trên nhu cầu của ứng dụng.
Nếu ứng dụng của bạn có những tính năng có thể sử dụng dữ liệu API của Google nhưng không bắt buộc trong các tính năng cốt lõi của ứng dụng, thì bạn nên thiết kế ứng dụng để có thể xử lý các trường hợp khi không truy cập được vào dữ liệu API một cách hiệu quả. 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 yêu cầu 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 biệt việc uỷ quyền với xác thực, bạn có thể tránh làm người dùng mới cảm thấy choáng ngợp hoặc khiến người dùng nhầm lẫn 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 dùng API Trình quản lý thông tin xác thực. Để uỷ quyền cho các thao tác cần có 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
- Mở dự án của bạn trong hoặc tạo một dự án nếu bạn chưa có.
- Trên trang , hãy đảm bảo rằng tất cả thông tin đều đầy đủ và chính xác.
- Đảm bảo rằng ứ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 được chỉ định chính xác. Những giá trị này sẽ được trình bày cho người dùng trên màn hình đồng ý Đăng nhập bằng Google khi đăng ký và 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.
- Trong , hãy tạo mã ứng dụng 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.
- Trong , 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ó. Hiện tại, bạn có thể bỏ qua các trường "Nguồn gốc JavaScript được uỷ quyền" và "URI chuyển hướng được uỷ quyền". Mã ứng dụng 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.
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 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:21.4.0"
}
Yêu cầu cấp các 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 thao tác cần có phạm vi bổ sung, hãy gọi AuthorizationClient.authorize()
. Ví dụ: nếu người dùng thực hiện một thao tác cần có quyền truy cập vào bộ nhớ của ứ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.launchIntentSenderRequest.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, trong đó chúng tôi giả định rằng việc này được thực hiện trong một mảnh. Mã này kiểm tra để đảm bảo các quyền cần thiết đã được cấp thành công, 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 (ApiException e) {
// 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 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ã thông báo 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ể trở nên 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ẽ chuyển đến máy chủ OAuth. Đoạn mã sau đây sẽ 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));
Nhận 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ột 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ù có thể dùng mã này để truy cập vào các tệp trên ổ đĩa 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 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 đăng nhập bao gồm thông tin người dùng, chẳng hạn như địa chỉ email và cũng đặ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 của mình. Yêu cầu uỷ quyền tiếp theo sẽ sử dụng tài khoản làm 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 là tài khoản mặc định.
Trong yêu cầu uỷ quyền, ngoài các phạm vi mà bạn muốn (ví dụ:
Drive scope
), hãy yêu cầu các phạm viuserinfo
,profile
vàopenid
. Sau khi mã truy cập được trả về, hãy lấy thông tin người dùng bằng cách gửi yêu cầu HTTPGET
đến điểm cuối userinfo của 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 vào tiêu đề, tương đương với lệnhcurl
sau:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
Phản hồi là
UserInfo
, chỉ 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 là tài khoản mặc định
Nếu bạn dùng Trình quản lý thông tin đăng nhập để 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 tiếp theo để uỷ quyền đề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()
của 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 mà không cần có sự tương tác của người dùng. 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 gọi là "mã làm mới".
Mã truy cập được thiết kế để có thời hạn 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ì khoảng thời gian 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. Sau khi hết hạn, mã thông báo sẽ không hợp lệ và mọi nỗ lực sử dụng mã thông báo này đều sẽ bị máy chủ tài nguyên từ chối. Vì mã truy cập chỉ tồn tại trong thời gian ngắn, nên các 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 tồn tại dài, được ứng dụng sử dụng để yêu cầu mã truy cập có thời gian tồn tại ngắn từ máy chủ uỷ quyền, khi mã truy cập cũ hết hạn mà không cần bất kỳ hoạt động tương tác nào của người dùng.
Để lấy 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 của mình bằng cách yêu cầu "quyền truy cập ngoại tuyến", sau đó trao đổ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ủ vì mã này có thể được sử 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ì lo ngại về vấn đề bảo mật. Thay vào đó, các mã này phải được lưu trữ trong các máy chủ phụ trợ của ứng dụng, nơi diễn ra quá trình trao đổi mã thông báo 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ể trao đổi mã này để 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.launchIntentSenderRequest.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 quá trình uỷ quyền 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
}
});
}