优化appauth登录

This commit is contained in:
CounterFire2023 2023-09-06 14:54:53 +08:00
parent f50a15b5ac
commit 04d21c8769
2 changed files with 423 additions and 343 deletions

View File

@ -11,15 +11,10 @@ import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.Window; import android.view.Window;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import com.bytedance.sdk.open.tiktok.TikTokOpenApiFactory; import com.bytedance.sdk.open.tiktok.TikTokOpenApiFactory;
@ -30,6 +25,7 @@ import com.bytedance.sdk.open.tiktok.base.VideoObject;
import com.bytedance.sdk.open.tiktok.share.Share; import com.bytedance.sdk.open.tiktok.share.Share;
import com.cege.games.release.activity.CustomCaptureActivity; import com.cege.games.release.activity.CustomCaptureActivity;
import com.cege.games.release.activity.WebPageActivity; import com.cege.games.release.activity.WebPageActivity;
import com.cege.games.release.appauth.AppAuthSvr;
import com.cege.games.release.apple.AppleLoginActivity; import com.cege.games.release.apple.AppleLoginActivity;
import com.cege.games.release.wallet.WalletUtil; import com.cege.games.release.wallet.WalletUtil;
import com.facebook.AccessToken; import com.facebook.AccessToken;
@ -54,8 +50,6 @@ import com.google.android.gms.tasks.Task;
import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.DriveScopes;
import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.analytics.FirebaseAnalytics;
import com.jc.jcfw.JcSDK; import com.jc.jcfw.JcSDK;
import com.jc.jcfw.appauth.AuthStateManager;
import com.jc.jcfw.appauth.JConfiguration;
import com.jc.jcfw.google.PayClient; import com.jc.jcfw.google.PayClient;
import com.jc.jcfw.util.JsonUtils; import com.jc.jcfw.util.JsonUtils;
import com.king.zxing.CameraScan; import com.king.zxing.CameraScan;
@ -63,22 +57,7 @@ import com.king.zxing.util.CodeUtils;
import com.king.zxing.util.LogUtils; import com.king.zxing.util.LogUtils;
import com.unity3d.player.UnityPlayerActivity; import com.unity3d.player.UnityPlayerActivity;
import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState; import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretBasic;
import net.openid.appauth.RegistrationRequest;
import net.openid.appauth.RegistrationResponse;
import net.openid.appauth.ResponseTypeValues;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import net.openid.appauth.browser.AnyBrowserMatcher;
import net.openid.appauth.browser.BrowserMatcher;
import org.cocos2dx.lib.Cocos2dxHelper; import org.cocos2dx.lib.Cocos2dxHelper;
import org.cocos2dx.lib.CocosJSHelper; import org.cocos2dx.lib.CocosJSHelper;
@ -86,13 +65,9 @@ import org.json.JSONException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions; import pub.devrel.easypermissions.EasyPermissions;
@ -126,14 +101,9 @@ public class MainActivity extends UnityPlayerActivity
private String mFunID; private String mFunID;
// AppAuth // AppAuth
private AuthorizationService mAuthService; private AppAuthSvr mAppAuthSvr;
private AuthStateManager mAuthStateManager;
private JConfiguration mConfiguration;
private ExecutorService mExecutor; private ExecutorService mExecutor;
private final AtomicReference<String> mClientId = new AtomicReference<>();
private final AtomicReference<AuthorizationRequest> mAuthRequest = new AtomicReference<>();
private final AtomicReference<CustomTabsIntent> mAuthIntent = new AtomicReference<>();
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
private TikTokOpenApi tiktokOpenApi; private TikTokOpenApi tiktokOpenApi;
@ -141,9 +111,6 @@ public class MainActivity extends UnityPlayerActivity
return mFunID; return mFunID;
} }
@NonNull
private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
private GoogleSignInClient mGoogleSignInClient; private GoogleSignInClient mGoogleSignInClient;
// facebook login // facebook login
private CallbackManager mCallbackManager; private CallbackManager mCallbackManager;
@ -152,7 +119,7 @@ public class MainActivity extends UnityPlayerActivity
private AppEventsLogger fbLogger; private AppEventsLogger fbLogger;
private WalletUtil mWalletUtil; private WalletUtil mWalletUtil;
private Consumer<String> loginCbAction = null;
public boolean isGooglePlayServicesAvailable() { public boolean isGooglePlayServicesAvailable() {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS; return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS;
@ -180,10 +147,9 @@ public class MainActivity extends UnityPlayerActivity
// end of google sign // end of google sign
// begin of google oauth sign // begin of google oauth sign
mAppAuthSvr = new AppAuthSvr(this);
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mAuthStateManager = AuthStateManager.getInstance(this); mAppAuthSvr.init(mExecutor);
mConfiguration = JConfiguration.getInstance(this);
mExecutor.submit(this::initializeAppAuth);
// end of google oauth sign // end of google oauth sign
// begin of facebook login // begin of facebook login
@ -217,22 +183,7 @@ public class MainActivity extends UnityPlayerActivity
parsePhoto(data); parsePhoto(data);
break; break;
case RC_AUTH: case RC_AUTH:
AuthorizationResponse response = AuthorizationResponse.fromIntent(data); mAppAuthSvr.parseLoginResult(data);
AuthorizationException ex = AuthorizationException.fromIntent(data);
if (response != null || ex != null) {
mAuthStateManager.updateAfterAuthorization(response, ex);
}
if (response != null && response.authorizationCode != null) {
// authorization code exchange is required
mAuthStateManager.updateAfterAuthorization(response, ex);
exchangeAuthorizationCode(response);
} else if (ex != null) {
Log.i(TAG, "Authorization flow failed: " + ex.getMessage());
JcSDK.nativeCb(mFunID, "Authorization flow failed: " + ex.getMessage(), null);
} else {
Log.i(TAG, "No authorization state retained - reauthorization required");
JcSDK.nativeCb(mFunID, "No authorization state retained - reauthorization required", null);
}
break; break;
case RC_SIGN_IN: case RC_SIGN_IN:
handleSignInResult(GoogleSignIn.getSignedInAccountFromIntent(data)); handleSignInResult(GoogleSignIn.getSignedInAccountFromIntent(data));
@ -427,18 +378,20 @@ public class MainActivity extends UnityPlayerActivity
} }
} else { } else {
Log.d(TAG, "no gms, use app auth"); Log.d(TAG, "no gms, use app auth");
AuthState state = mAuthStateManager.getCurrent(); AuthState state = mAppAuthSvr.getCurrentState();
if (state.isAuthorized()) { if (state.isAuthorized()) {
if (state.getNeedsTokenRefresh()) { if (state.getNeedsTokenRefresh()) {
Log.w(TAG, "need refresh accessToken"); Log.w(TAG, "need refresh accessToken");
performTokenRequest(state.createTokenRefreshRequest(), this::handleCodeExchangeResponse); mAppAuthSvr.refreshToken(funId, null);
} else { } else {
Log.w(TAG, "already login, accessToken not expired, id token:: " + state.getIdToken()); Log.w(TAG, "already login, accessToken not expired, id token:: " + state.getIdToken());
JcSDK.nativeCb(this.mFunID, null, state.getIdToken()); JcSDK.nativeCb(this.mFunID, null, state.getIdToken());
} }
} else { } else {
Log.w(TAG, "not login"); Log.w(TAG, "not login");
mExecutor.submit(this::doAuth); mExecutor.submit(() -> {
mAppAuthSvr.doAuth(funId, null);
});
} }
} }
} }
@ -466,271 +419,6 @@ public class MainActivity extends UnityPlayerActivity
} }
} }
// begin of AppAuth
/**
* Initializes the authorization service configuration if necessary, either from
* the local
* static values or by retrieving an OpenID discovery document.
*/
@WorkerThread
private void initializeAppAuth() {
Log.i(TAG, "Initializing AppAuth");
recreateAuthorizationService();
if (mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration() != null) {
// configuration is already created, skip to client initialization
Log.i(TAG, "auth config already established");
initializeClient();
return;
}
// if we are not using discovery, build the authorization service configuration
// directly
// from the static configuration values.
if (mConfiguration.getDiscoveryUri() == null) {
Log.i(TAG, "Creating auth config from res/raw/auth_config.json");
AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
mConfiguration.getAuthEndpointUri(),
mConfiguration.getTokenEndpointUri(),
mConfiguration.getRegistrationEndpointUri(),
mConfiguration.getEndSessionEndpoint());
mAuthStateManager.replace(new AuthState(config));
initializeClient();
return;
}
// WrongThread inference is incorrect for lambdas
// noinspection WrongThread
Log.i(TAG, "Retrieving OpenID discovery doc");
AuthorizationServiceConfiguration.fetchFromUrl(
mConfiguration.getDiscoveryUri(),
this::handleConfigurationRetrievalResult,
mConfiguration.getConnectionBuilder());
}
@MainThread
private void handleConfigurationRetrievalResult(
AuthorizationServiceConfiguration config,
AuthorizationException ex) {
if (config == null) {
Log.i(TAG, "Failed to retrieve discovery document", ex);
return;
}
Log.i(TAG, "Discovery document retrieved");
mAuthStateManager.replace(new AuthState(config));
mExecutor.submit(this::initializeClient);
}
private void recreateAuthorizationService() {
if (mAuthService != null) {
Log.i(TAG, "Discarding existing AuthService instance");
mAuthService.dispose();
}
mAuthService = createAuthorizationService();
mAuthRequest.set(null);
mAuthIntent.set(null);
}
private AuthorizationService createAuthorizationService() {
Log.i(TAG, "Creating authorization service");
AppAuthConfiguration.Builder builder = new AppAuthConfiguration.Builder();
builder.setBrowserMatcher(mBrowserMatcher);
builder.setConnectionBuilder(mConfiguration.getConnectionBuilder());
return new AuthorizationService(this, builder.build());
}
private void createAuthRequest(@Nullable String loginHint) {
Log.i(TAG, "Creating auth request for login hint: " + loginHint);
AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
mClientId.get(),
ResponseTypeValues.CODE,
mConfiguration.getRedirectUri())
// apple need `form_post` when authorization_scope has `name email `
// .setResponseMode("form_post")
// .setResponseType("code id_token")
.setScope(mConfiguration.getScope());
if (!TextUtils.isEmpty(loginHint)) {
authRequestBuilder.setLoginHint(loginHint);
}
mAuthRequest.set(authRequestBuilder.build());
}
private void warmUpBrowser() {
mAuthIntentLatch = new CountDownLatch(1);
mExecutor.execute(() -> {
Log.i(TAG, "Warming up browser instance for auth request");
Uri uri = mAuthRequest.get().toUri();
Log.d(TAG, "URI: " + uri);
CustomTabsIntent.Builder intentBuilder = mAuthService.createCustomTabsIntentBuilder(uri);
mAuthIntent.set(intentBuilder.build());
mAuthIntentLatch.countDown();
});
}
@MainThread
private void initializeAuthRequest() {
createAuthRequest("");
warmUpBrowser();
displayAuthOptions();
}
/**
* Initiates a dynamic registration request if a client ID is not provided by
* the static
* configuration.
*/
@WorkerThread
private void initializeClient() {
if (mConfiguration.getClientId() != null) {
Log.i(TAG, "Using static client ID: " + mConfiguration.getClientId());
// use a statically configured client ID
mClientId.set(mConfiguration.getClientId());
runOnUiThread(this::initializeAuthRequest);
return;
}
RegistrationResponse lastResponse = mAuthStateManager.getCurrent().getLastRegistrationResponse();
if (lastResponse != null) {
Log.i(TAG, "Using dynamic client ID: " + lastResponse.clientId);
// already dynamically registered a client ID
mClientId.set(lastResponse.clientId);
runOnUiThread(this::initializeAuthRequest);
return;
}
// WrongThread inference is incorrect for lambdas
// noinspection WrongThread
Log.i(TAG, "Dynamically registering client");
RegistrationRequest registrationRequest = new RegistrationRequest.Builder(
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
Collections.singletonList(mConfiguration.getRedirectUri()))
.setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME)
.build();
mAuthService.performRegistrationRequest(
registrationRequest,
this::handleRegistrationResponse);
}
private void handleRegistrationResponse(
RegistrationResponse response,
AuthorizationException ex) {
mAuthStateManager.updateAfterRegistration(response, ex);
if (response == null) {
Log.i(TAG, "Failed to dynamically register client", ex);
return;
}
Log.i(TAG, "Dynamically registered client: " + response.clientId);
mClientId.set(response.clientId);
initializeAuthRequest();
}
private void displayAuthOptions() {
AuthState state = mAuthStateManager.getCurrent();
AuthorizationServiceConfiguration config = state.getAuthorizationServiceConfiguration();
String authEndpointStr;
if (config.discoveryDoc != null) {
authEndpointStr = "Discovered auth endpoint: \n";
} else {
authEndpointStr = "Static auth endpoint: \n";
}
authEndpointStr += config.authorizationEndpoint;
Log.i(TAG, authEndpointStr);
String clientIdStr;
if (state.getLastRegistrationResponse() != null) {
clientIdStr = "Dynamic client ID: \n";
} else {
clientIdStr = "Static client ID: \n";
}
clientIdStr += mClientId;
Log.i(TAG, clientIdStr);
}
/**
* Performs the authorization request, using the browser selected in the
* spinner,
* and a user-provided `login_hint` if available.
*/
@WorkerThread
private void doAuth() {
try {
mAuthIntentLatch.await();
} catch (InterruptedException ex) {
Log.w(TAG, "Interrupted while waiting for auth intent");
}
Intent intent = mAuthService.getAuthorizationRequestIntent(
mAuthRequest.get(),
mAuthIntent.get());
startActivityForResult(intent, RC_AUTH);
}
@MainThread
private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
Log.d(TAG, "Exchanging authorization code");
performTokenRequest(
authorizationResponse.createTokenExchangeRequest(),
this::handleCodeExchangeResponse);
}
@MainThread
private void performTokenRequest(
TokenRequest request,
AuthorizationService.TokenResponseCallback callback) {
ClientAuthentication clientAuthentication;
try {
clientAuthentication = mAuthStateManager.getCurrent().getClientAuthentication();
} catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
Log.d(TAG, "Token request cannot be made, client authentication for the token "
+ "endpoint could not be constructed (%s)", ex);
return;
}
mAuthService.performTokenRequest(
request,
clientAuthentication,
callback);
}
private void checkAuthStateAndCB() {
AuthState state = mAuthStateManager.getCurrent();
Log.d(TAG, "login success, auth state: " + state.isAuthorized());
Log.d(TAG, "id token : " + state.getIdToken());
Log.d(TAG, "access token: " + state.getAccessToken());
Log.d(TAG, "refresh token: " + state.getRefreshToken());
mAuthStateManager.replace(state);
if (loginCbAction != null) {
mExecutor.submit(() -> {
loginCbAction.accept("success");
loginCbAction = null;
});
} else {
JcSDK.nativeCb(this.mFunID, null, state.getIdToken());
}
}
@WorkerThread
private void handleCodeExchangeResponse(
@Nullable TokenResponse tokenResponse,
@Nullable AuthorizationException authException) {
mAuthStateManager.updateAfterTokenResponse(tokenResponse, authException);
if (!mAuthStateManager.getCurrent().isAuthorized()) {
final String message = "Authorization Code exchange failed: "
+ ((authException != null) ? authException.error : "");
Log.d(TAG, message);
JcSDK.nativeCb(mFunID, message, null);
} else {
checkAuthStateAndCB();
}
}
// sign with tiktok // sign with tiktok
public void signWithTiktok(String funId) { public void signWithTiktok(String funId) {
@ -909,24 +597,23 @@ public class MainActivity extends UnityPlayerActivity
mWalletUtil.uploadCfgWithGPS(_result -> {}); mWalletUtil.uploadCfgWithGPS(_result -> {});
} }
} else { } else {
AuthState state = mAuthStateManager.getCurrent(); AuthState state = mAppAuthSvr.getCurrentState();
if (state.isAuthorized()) { if (state.isAuthorized()) {
if (state.getNeedsTokenRefresh()) { if (state.getNeedsTokenRefresh()) {
Log.d(TAG, "need refresh accessToken"); Log.d(TAG, "need refresh accessToken");
loginCbAction = _result -> { mAppAuthSvr.refreshToken(funID, _result -> {
mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{});
}; });
performTokenRequest(state.createTokenRefreshRequest(),
this::handleCodeExchangeResponse);
} else { } else {
mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{});
} }
} else { } else {
Log.w(TAG, "not login"); Log.w(TAG, "not login");
loginCbAction = _result -> { mExecutor.submit(() -> {
mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); mAppAuthSvr.doAuth(funID, _result -> {
}; mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{});
mExecutor.submit(this::doAuth); });
});
} }
} }
}); });
@ -960,20 +647,18 @@ public class MainActivity extends UnityPlayerActivity
} }
} else { } else {
if (!mWalletUtil.localCfgExists(account)) { if (!mWalletUtil.localCfgExists(account)) {
AuthState state = mAuthStateManager.getCurrent(); AuthState state = mAppAuthSvr.getCurrentState();
// 1. check whether the google account has been logged in, this situation occurs when non-google login // 1. check whether the google account has been logged in, this situation occurs when non-google login
// 2. check if already had permission of drive appdata, add permission of drive appdata to auth_config // 2. check if already had permission of drive appdata, add permission of drive appdata to auth_config
// 3. check if need refresh access token // 3. check if need refresh access token
if (state.isAuthorized()) { if (state.isAuthorized()) {
if (state.getNeedsTokenRefresh()) { if (state.getNeedsTokenRefresh()) {
Log.d(TAG, "need refresh accessToken"); Log.d(TAG, "need refresh accessToken");
loginCbAction = _result -> { mAppAuthSvr.refreshToken(funID, _result -> {
mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> {
mWalletUtil.getPassLocal(); mWalletUtil.getPassLocal();
}); });
}; });
performTokenRequest(state.createTokenRefreshRequest(),
this::handleCodeExchangeResponse);
} else { } else {
Log.d(TAG, "access token no need refresh"); Log.d(TAG, "access token no need refresh");
mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> {
@ -982,12 +667,13 @@ public class MainActivity extends UnityPlayerActivity
} }
} else { } else {
Log.w(TAG, "not login"); Log.w(TAG, "not login");
loginCbAction = _result -> { mExecutor.submit(() -> {
mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { mAppAuthSvr.doAuth(funID, _result -> {
mWalletUtil.getPassLocal(); mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> {
mWalletUtil.getPassLocal();
});
}); });
}; });
mExecutor.submit(this::doAuth);
} }
} else { } else {
mWalletUtil.getPassLocal(); mWalletUtil.getPassLocal();

View File

@ -0,0 +1,394 @@
package com.cege.games.release.appauth;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.browser.customtabs.CustomTabsIntent;
import com.jc.jcfw.JcSDK;
import com.jc.jcfw.appauth.AuthStateManager;
import com.jc.jcfw.appauth.JConfiguration;
import com.jc.jcfw.util.ThreadUtils;
import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretBasic;
import net.openid.appauth.RegistrationRequest;
import net.openid.appauth.RegistrationResponse;
import net.openid.appauth.ResponseTypeValues;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import net.openid.appauth.browser.AnyBrowserMatcher;
import net.openid.appauth.browser.BrowserMatcher;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
public class AppAuthSvr {
private final String TAG = getClass().getSimpleName();
private final Activity activity;
private AuthorizationService mAuthService;
private AuthStateManager mAuthStateManager;
private JConfiguration mConfiguration;
private ExecutorService mExecutor;
private final AtomicReference<String> mClientId = new AtomicReference<>();
private final AtomicReference<AuthorizationRequest> mAuthRequest = new AtomicReference<>();
private final AtomicReference<CustomTabsIntent> mAuthIntent = new AtomicReference<>();
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
private String mFunID;
private static final int RC_AUTH = 0X04;
private Consumer<String> loginCbAction = null;
@NonNull
private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
public AppAuthSvr(Activity activity) {
this.activity = activity;
}
public void init(ExecutorService executorService) {
mExecutor = executorService;
mAuthStateManager = AuthStateManager.getInstance(activity);
mConfiguration = JConfiguration.getInstance(activity);
mExecutor.submit(this::initializeAppAuth);
}
public void setFunID(String funID) {
this.mFunID = funID;
}
public void setLoginCbAction(Consumer<String> func) {
this.loginCbAction = func;
}
public AuthState getCurrentState() {
return mAuthStateManager.getCurrent();
}
/**
* Performs the authorization request, using the browser selected in the
* spinner,
* and a user-provided `login_hint` if available.
*/
@WorkerThread
public void doAuth(String funID, Consumer<String> func) {
this.mFunID = funID;
this.loginCbAction = func;
try {
mAuthIntentLatch.await();
} catch (InterruptedException ex) {
Log.w(TAG, "Interrupted while waiting for auth intent");
}
Intent intent = mAuthService.getAuthorizationRequestIntent(
mAuthRequest.get(),
mAuthIntent.get());
activity.startActivityForResult(intent, RC_AUTH);
}
public void parseLoginResult(@NonNull Intent data) {
AuthorizationResponse response = AuthorizationResponse.fromIntent(data);
AuthorizationException ex = AuthorizationException.fromIntent(data);
if (response != null || ex != null) {
mAuthStateManager.updateAfterAuthorization(response, ex);
}
if (response != null && response.authorizationCode != null) {
// authorization code exchange is required
mAuthStateManager.updateAfterAuthorization(response, ex);
exchangeAuthorizationCode(response);
} else if (ex != null) {
Log.i(TAG, "Authorization flow failed: " + ex.getMessage());
errorCB("Authorization flow failed: " + ex.getMessage());
} else {
Log.i(TAG, "No authorization state retained - reauthorization required");
errorCB("No authorization state retained - reauthorization required");
}
}
public void refreshToken(String funID, Consumer<String> func) {
this.mFunID = funID;
this.loginCbAction = func;
AuthState state = getCurrentState();
performTokenRequest(state.createTokenRefreshRequest(), this::handleCodeExchangeResponse);
}
/**
* Initializes the authorization service configuration if necessary, either from
* the local
* static values or by retrieving an OpenID discovery document.
*/
@WorkerThread
private void initializeAppAuth() {
Log.i(TAG, "Initializing AppAuth");
recreateAuthorizationService();
if (mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration() != null) {
// configuration is already created, skip to client initialization
Log.i(TAG, "auth config already established");
initializeClient();
return;
}
// if we are not using discovery, build the authorization service configuration
// directly
// from the static configuration values.
if (mConfiguration.getDiscoveryUri() == null) {
Log.i(TAG, "Creating auth config from res/raw/auth_config.json");
AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
mConfiguration.getAuthEndpointUri(),
mConfiguration.getTokenEndpointUri(),
mConfiguration.getRegistrationEndpointUri(),
mConfiguration.getEndSessionEndpoint());
mAuthStateManager.replace(new AuthState(config));
initializeClient();
return;
}
// WrongThread inference is incorrect for lambdas
// noinspection WrongThread
Log.i(TAG, "Retrieving OpenID discovery doc");
AuthorizationServiceConfiguration.fetchFromUrl(
mConfiguration.getDiscoveryUri(),
this::handleConfigurationRetrievalResult,
mConfiguration.getConnectionBuilder());
}
@MainThread
private void handleConfigurationRetrievalResult(
AuthorizationServiceConfiguration config,
AuthorizationException ex) {
if (config == null) {
Log.i(TAG, "Failed to retrieve discovery document", ex);
return;
}
Log.i(TAG, "Discovery document retrieved");
mAuthStateManager.replace(new AuthState(config));
mExecutor.submit(this::initializeClient);
}
private void recreateAuthorizationService() {
if (mAuthService != null) {
Log.i(TAG, "Discarding existing AuthService instance");
mAuthService.dispose();
}
mAuthService = createAuthorizationService();
mAuthRequest.set(null);
mAuthIntent.set(null);
}
private AuthorizationService createAuthorizationService() {
Log.i(TAG, "Creating authorization service");
AppAuthConfiguration.Builder builder = new AppAuthConfiguration.Builder();
builder.setBrowserMatcher(mBrowserMatcher);
builder.setConnectionBuilder(mConfiguration.getConnectionBuilder());
return new AuthorizationService(activity, builder.build());
}
private void createAuthRequest(@Nullable String loginHint) {
Log.i(TAG, "Creating auth request for login hint: " + loginHint);
AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
mClientId.get(),
ResponseTypeValues.CODE,
mConfiguration.getRedirectUri())
// apple need `form_post` when authorization_scope has `name email `
// .setResponseMode("form_post")
// .setResponseType("code id_token")
.setScope(mConfiguration.getScope());
if (!TextUtils.isEmpty(loginHint)) {
authRequestBuilder.setLoginHint(loginHint);
}
mAuthRequest.set(authRequestBuilder.build());
}
private void warmUpBrowser() {
mAuthIntentLatch = new CountDownLatch(1);
mExecutor.execute(() -> {
Log.i(TAG, "Warming up browser instance for auth request");
Uri uri = mAuthRequest.get().toUri();
Log.d(TAG, "URI: " + uri);
CustomTabsIntent.Builder intentBuilder = mAuthService.createCustomTabsIntentBuilder(uri);
mAuthIntent.set(intentBuilder.build());
mAuthIntentLatch.countDown();
});
}
@MainThread
private void initializeAuthRequest() {
createAuthRequest("");
warmUpBrowser();
displayAuthOptions();
}
/**
* Initiates a dynamic registration request if a client ID is not provided by
* the static
* configuration.
*/
@WorkerThread
private void initializeClient() {
if (mConfiguration.getClientId() != null) {
Log.i(TAG, "Using static client ID: " + mConfiguration.getClientId());
// use a statically configured client ID
mClientId.set(mConfiguration.getClientId());
ThreadUtils.runInMain(this::initializeAuthRequest);
return;
}
RegistrationResponse lastResponse = mAuthStateManager.getCurrent().getLastRegistrationResponse();
if (lastResponse != null) {
Log.i(TAG, "Using dynamic client ID: " + lastResponse.clientId);
// already dynamically registered a client ID
mClientId.set(lastResponse.clientId);
ThreadUtils.runInMain(this::initializeAuthRequest);
return;
}
// WrongThread inference is incorrect for lambdas
// noinspection WrongThread
Log.i(TAG, "Dynamically registering client");
RegistrationRequest registrationRequest = new RegistrationRequest.Builder(
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
Collections.singletonList(mConfiguration.getRedirectUri()))
.setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME)
.build();
mAuthService.performRegistrationRequest(
registrationRequest,
this::handleRegistrationResponse);
}
private void handleRegistrationResponse(
RegistrationResponse response,
AuthorizationException ex) {
mAuthStateManager.updateAfterRegistration(response, ex);
if (response == null) {
Log.i(TAG, "Failed to dynamically register client", ex);
return;
}
Log.i(TAG, "Dynamically registered client: " + response.clientId);
mClientId.set(response.clientId);
initializeAuthRequest();
}
private void displayAuthOptions() {
AuthState state = mAuthStateManager.getCurrent();
AuthorizationServiceConfiguration config = state.getAuthorizationServiceConfiguration();
String authEndpointStr;
if (config.discoveryDoc != null) {
authEndpointStr = "Discovered auth endpoint: \n";
} else {
authEndpointStr = "Static auth endpoint: \n";
}
authEndpointStr += config.authorizationEndpoint;
Log.i(TAG, authEndpointStr);
String clientIdStr;
if (state.getLastRegistrationResponse() != null) {
clientIdStr = "Dynamic client ID: \n";
} else {
clientIdStr = "Static client ID: \n";
}
clientIdStr += mClientId;
Log.i(TAG, clientIdStr);
}
@MainThread
private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
Log.d(TAG, "Exchanging authorization code");
performTokenRequest(
authorizationResponse.createTokenExchangeRequest(),
this::handleCodeExchangeResponse);
}
@MainThread
private void performTokenRequest(
TokenRequest request,
AuthorizationService.TokenResponseCallback callback) {
ClientAuthentication clientAuthentication;
try {
clientAuthentication = mAuthStateManager.getCurrent().getClientAuthentication();
} catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
Log.d(TAG, "Token request cannot be made, client authentication for the token "
+ "endpoint could not be constructed (%s)", ex);
return;
}
mAuthService.performTokenRequest(
request,
clientAuthentication,
callback);
}
@WorkerThread
private void handleCodeExchangeResponse(
@Nullable TokenResponse tokenResponse,
@Nullable AuthorizationException authException) {
mAuthStateManager.updateAfterTokenResponse(tokenResponse, authException);
if (!mAuthStateManager.getCurrent().isAuthorized()) {
final String message = "Authorization Code exchange failed: "
+ ((authException != null) ? authException.error : "");
Log.d(TAG, message);
errorCB(message);
} else {
checkAuthStateAndCB();
}
}
private void checkAuthStateAndCB() {
AuthState state = mAuthStateManager.getCurrent();
Log.d(TAG, "login success, auth state: " + state.isAuthorized());
Log.d(TAG, "id token : " + state.getIdToken());
Log.d(TAG, "access token: " + state.getAccessToken());
Log.d(TAG, "refresh token: " + state.getRefreshToken());
mAuthStateManager.replace(state);
if (loginCbAction != null) {
mExecutor.submit(() -> {
loginCbAction.accept("success");
clearParams();
});
} else {
successCB(state.getIdToken());
}
}
private void clearParams() {
this.mFunID = null;
this.loginCbAction = null;
}
public void errorCB(String msg) {
JcSDK.nativeCb(mFunID, msg, null);
clearParams();
}
public void successCB(String dataStr) {
JcSDK.nativeCb(mFunID, null, dataStr);
clearParams();
}
}