투케이2K

151. (TWOK/UTIL) [Android/Java] C_SimpleAuth_Module : 휴대폰 간편 심플 인증 관련 모듈 본문

투케이2K 유틸파일

151. (TWOK/UTIL) [Android/Java] C_SimpleAuth_Module : 휴대폰 간편 심플 인증 관련 모듈

투케이2K 2024. 12. 13. 08:55

[설 명]

프로그램 : Android / Java

설 명 : C_SimpleAuth_Module : 휴대폰 간편 심플 인증 관련 모듈

 

[소스 코드]

 

package com.example.javaproject.C_Module;

import static android.content.Context.FINGERPRINT_SERVICE;
import static android.content.Context.KEYGUARD_SERVICE;

import android.Manifest;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.example.javaproject.S_Log;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;

public class C_SimpleAuth_Module {


    /**
     * // --------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // --------------------------------------------------------------------------------------
     * 1. [설명] : 휴대폰 간편 심플 인증 관련 모듈
     * // --------------------------------------------------------------------------------------
     * 2. 필요 퍼미션 :
     *
     * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     * <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     * <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     * <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
     * // --------------------------------------------------------------------------------------
     * 3. 제약 조건 :
     *
     * 안드로이드 디바이스 기기내 지문인증 기능을 사용하기 위해서는 마시멜로 버전 이상이어야합니다
     * 지문인증 기능을 사용하기 위해서는 안드로이드 시스템 설정 내에 보안 설정 >> 잠금 설정 >> 지문 설정이되어야합니다
     * // --------------------------------------------------------------------------------------
     * */





    /**
     * // --------------------------------------------------------------------------------------
     * TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // --------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableSimpleAuth : 간편 인증 수행 실시
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     */





    // ------------------------------------------------------------------------------------------
    // TODO [전역 변수 선언]
    // ------------------------------------------------------------------------------------------
    private static String ACTIVITY_NAME = "C_SimpleAuth_Module";

    private static ObservableEmitter returnData = null; // [인증 결과 값 반환 변수 선언]
    private static String errorMessage = ""; // [리턴 에러 메시지]



    // TODO [지문 인증에 필요한 객체 선언 실시]
    private static final String KEY_NAME = "simpleAuth_twok_key";
    private static FingerprintManager fingerprintManager;
    private static KeyguardManager keyguardManager;
    private static KeyStore keyStore;
    private static KeyGenerator keyGenerator;
    public static Cipher cipher;
    private static FingerprintManager.CryptoObject cryptoObject;



    // TODO [메시지 표시 내용 정의]
    private static final String ERROR_USE_NO_DEVICE = "지문을 사용할 수 없는 디바이스 입니다.";
    private static final String ERROR_PERMISSION_IS_NO = "지문 인증 사용을 위한 권한을 허용해 주세요.";
    private static final String ERROR_DEVICE_LOCK_NO = "지문 인증을 사용하기 위해서는 디바이스 잠금 화면을 설정해 주세요.";
    private static final String ERROR_FINGER_REG_NO = "잠금 설정에 등록된 지문이 없습니다. 지문을 먼저 등록해주세요.";
    private static final String ERROR_VERSION_NO_DEVICE = "지문을 사용할 수 없는 하위 버전의 디바이스 입니다.";
    private static final String ING_FINGER_START_MSG = "손가락을 지문인식 센서에 대 주세요.";
    private static final String FAIL_FINGER_AUTH = "지문 인증 실패 ... 다시 시도해주세요.";
    private static final String SUCCESS_FINGER_AUTH = "지문 인증에 성공했습니다.";

    private static final String SUCCESS_FINGER_AUTH = "지문 인증에 성공했습니다.";
    private static final String ERROR_INIT_FAIL = "지문 인증 초기화에 문제가 발생 했습니다. (generateKey , cipherInit)";
    private static final String EXCEPTION_MESSAGE = "지문 인증 진행 중 예외 문제가 발생 했습니다. (Exception)";





    // ------------------------------------------------------------------------------------------
    // TODO [SEARCH FAST] : observableSimpleAuth : 간편 인증 수행 실시
    // ------------------------------------------------------------------------------------------
    // TODO [호출 방법 소스 코드]
    // -----------------------------------------------------------------------------------------
    /*
    try {
        C_SimpleAuth_Module.observableSimpleAuth(A_Intro.this)
                .subscribeOn(AndroidSchedulers.mainThread()) // [Observable (생성자) 로직을 IO 스레드에서 실행 : 백그라운드]
                .observeOn(Schedulers.io()) // [Observer (관찰자) 로직을 메인 스레드에서 실행]
                .subscribe(new Observer<Boolean>() { // [Observable.create 타입 지정]
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                    }
                    @Override
                    public void onNext(@NonNull Boolean value) {
                        S_Log._D_(ACTIVITY_NAME + " :: observableSimpleAuth :: 간편 인증 수행 :: onNext", new String[]{String.valueOf(value)});
                    }
                    @Override
                    public void onError(@NonNull Throwable e) {
                        S_Log._E_(ACTIVITY_NAME + " :: observableSimpleAuth :: 간편 인증 수행 :: onError", new String[]{String.valueOf(e.getMessage())});
                    }
                    @Override
                    public void onComplete() {
                    }
                });
    }
    catch (Exception e){
        e.printStackTrace();
    }
    */
    // -----------------------------------------------------------------------------------------
    public static Observable<Boolean> observableSimpleAuth(Context mContext){
        // ===============================================================
        S_Log._D_(ACTIVITY_NAME + " :: observableSimpleAuth :: 간편 인증 수행 실시", null);
        // ===============================================================

        return Observable.create(subscriber -> {

            // ------------------------------------------------------
            // TODO [ObservableEmitter 할당]
            // ------------------------------------------------------
            returnData = subscriber;
            errorMessage = "";


            // ------------------------------------------------------
            // TODO [로직 처리 실시]
            // ------------------------------------------------------
            try {

                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // TODO [안드로이드 마시멜로우 부터 사용 가능]

                    // TODO [Manifest.xml 에 Fingerprint 퍼미션을 추가해 워야 사용가능]
                    fingerprintManager = (FingerprintManager) mContext.getSystemService(FINGERPRINT_SERVICE);
                    keyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);


                    // TODO [지문을 사용할 수 없는 디바이스인 경우 체크 수행]
                    boolean deviceCheckFlag = false;

                    if(!fingerprintManager.isHardwareDetected()){
                        errorMessage = ERROR_USE_NO_DEVICE; // [ERROR] : 지문 인증을 사용할 수 없는 디바이스입니다.
                    }
                    else if(ContextCompat.checkSelfPermission(mContext, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED){ // TODO [지문 인증 사용을 거부한 경우]
                        errorMessage = ERROR_PERMISSION_IS_NO; // [ERROR] : 지문 인증을 사용하기 위한 권한 부여 상태를 확인해주세요.
                    }
                    else if(!keyguardManager.isKeyguardSecure()){ // TODO [잠금 설정이 없는 경우]
                        errorMessage = ERROR_DEVICE_LOCK_NO; // [ERROR] : 잠금 설정 등록 여부를 확인해주세요.
                    }
                    else if(!fingerprintManager.hasEnrolledFingerprints()){ // TODO [잠금 설정에 등록된 지문이 없는 경우]
                        errorMessage = ERROR_FINGER_REG_NO; // [ERROR] : 잠금 설정에 등록된 지문 여부를 확인해주세요.
                    }
                    else { // TODO [모든 관문을 성공적으로 통과 (지문인식을 지원하고 지문 사용이 허용되어 있고 잠금화면이 설정되었고 지문이 등록되어 있을때)]
                        deviceCheckFlag = true;
                    }


                    // TODO [로직 분기 처리]
                    if (deviceCheckFlag == true){ // [디바이스 지원 및 잠금 정상 확인]

                        // [지문 인증 실행]
                        try {

                            boolean createKey = generateKey();
                            boolean initFlag = cipherInit();

                            if (createKey == true && initFlag == true){

                                // [CryptoObject 생성]
                                cryptoObject = new FingerprintManager.CryptoObject(cipher);

                                // TODO [지문 인증 실행]
                                C_FingerprintHandler fingerprintHandler = new C_FingerprintHandler(mContext);
                                fingerprintHandler.startAuth(fingerprintManager, cryptoObject);

                            }
                            else {

                                // [리턴 데이터 반환]
                                errorMessage = ERROR_INIT_FAIL; // [ERROR] : [지문 인증 초기화에 문제가 발생 했습니다. (generateKey , cipherInit)]

                                try {
                                    if (returnData != null && returnData.isDisposed() == false){
                                        returnData.onError(new Throwable(String.valueOf(errorMessage)));
                                        returnData.onComplete();
                                        returnData = null;
                                    }
                                }
                                catch (Exception ew){}

                            }

                        }
                        catch (Exception e){
                            e.printStackTrace();

                            // [리턴 데이터 반환]
                            errorMessage = EXCEPTION_MESSAGE + " : [1] : " + String.valueOf(e.getMessage());

                            try {
                                if (returnData != null && returnData.isDisposed() == false){
                                    returnData.onError(new Throwable(String.valueOf(errorMessage)));
                                    returnData.onComplete();
                                    returnData = null;
                                }
                            }
                            catch (Exception ew){}

                        }
                    }
                    else {
                        if (returnData != null && returnData.isDisposed() == false){
                            returnData.onError(new Throwable(String.valueOf(errorMessage)));
                            returnData.onComplete();
                            returnData = null;
                        }
                    }

                }
                else { // TODO [디바이스가 마시멜로 이하인 경우]

                    // [리턴 데이터 반환]
                    errorMessage = ERROR_VERSION_NO_DEVICE; // [ERROR] : Android Os Version Not Supported

                    if (returnData != null && returnData.isDisposed() == false){
                        returnData.onError(new Throwable(String.valueOf(errorMessage)));
                        returnData.onComplete();
                        returnData = null;
                    }
                }
            }
            catch (final Exception e){
                e.printStackTrace();

                // [리턴 데이터 반환]
                errorMessage = EXCEPTION_MESSAGE + " : [2] : " + String.valueOf(e.getMessage());

                try {
                    if (returnData != null && returnData.isDisposed() == false){
                        returnData.onError(new Throwable(String.valueOf(errorMessage)));
                        returnData.onComplete();
                        returnData = null;
                    }
                }
                catch (Exception ew){}
            }

        });
    }





    // ------------------------------------------------------------------------------------------
    // TODO [암호화 된 지문 관리자를 만드는 데 사용할 암호를 초기화 메소드]
    // ------------------------------------------------------------------------------------------

    // ------------------------------------------------------------------------------------------
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static boolean cipherInit(){
        // ===============================================================
        S_Log._D_(ACTIVITY_NAME + " :: cipherInit :: 지문 인증 암호 초기화 수행 실시", null);
        // ===============================================================

        try {

            // [Cipher.getInstance]
            cipher = Cipher.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES + "/"
                            + KeyProperties.BLOCK_MODE_CBC + "/"
                            + KeyProperties.ENCRYPTION_PADDING_PKCS7);

            // [keyStore.load]
            keyStore.load(null);
            SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
            cipher.init(Cipher.ENCRYPT_MODE, key);

            // [Return]
            return true;

        }
        catch (Exception e) {
            e.printStackTrace();

            // [Log]
            S_Log._E_(ACTIVITY_NAME + " :: cipherInit :: 지문 인증 암호 초기화 수행 에러", new String[]{"Error :: " + String.valueOf(e.getMessage())});

            // [Return]
            return false;
        }
    }





    // ------------------------------------------------------------------------------------------
    // TODO [비밀 키를 생성하는 메소드]
    // ------------------------------------------------------------------------------------------
    @RequiresApi(api = Build.VERSION_CODES.M)
    protected static boolean generateKey() {
        // ===============================================================
        S_Log._D_(ACTIVITY_NAME + " :: generateKey :: 비밀 키 생성 실시", null);
        // ===============================================================

        try {

            // [KeyStore.getInstance]
            keyStore = KeyStore.getInstance("AndroidKeyStore");


            // [KeyGenerator.getInstance]
            keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");


            // [keyGenerator.generateKey]
            keyStore.load(null);

            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build());
            keyGenerator.generateKey();


            // [Return]
            return true;

        } catch (Exception e) {
            e.printStackTrace();

            // [Log]
            S_Log._E_(ACTIVITY_NAME + " :: generateKey :: 비밀 키 생성 에러", new String[]{"Error :: " + String.valueOf(e.getMessage())});

            // [Return]
            return false;
        }

    }





    // ------------------------------------------------------------------------------------------
    // TODO [지문 인식 수행 부분] : [핸들러]
    // ------------------------------------------------------------------------------------------
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static class C_FingerprintHandler extends FingerprintManager.AuthenticationCallback {

        // TODO [지문 인식 객체 선언]
        CancellationSignal cancellationSignal;
        private Context mContext;


        // [클래스 생성자 초기화]
        public C_FingerprintHandler(Context context){
            this.mContext = context;
        }


        // [지문 인식 인증 시작 메소드]
        @RequiresApi(api = Build.VERSION_CODES.M)
        public void startAuth(FingerprintManager fingerprintManager, FingerprintManager.CryptoObject cryptoObject) {
            // ===============================================================
            S_Log._W_(ACTIVITY_NAME + " :: startAuth :: 지문 인증 시작 실시", new String[]{String.valueOf(ING_FINGER_START_MSG)});
            // ===============================================================

            try {

                // [지문 인증 수행 실시]
                cancellationSignal = new CancellationSignal();
                fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null);

                // [토스트 메시지 표시]
                try { if (this.mContext != null){ Toast.makeText(this.mContext, String.valueOf(ING_FINGER_START_MSG), Toast.LENGTH_SHORT).show(); } } catch (Exception es){}

            }
            catch (Exception e){
                e.printStackTrace();
            }
        }


        // [지문 인식 인증 에러 메소드]
        @Override
        public void onAuthenticationError(int errorCode, CharSequence errString) {
            // ===============================================================
            S_Log._E_(ACTIVITY_NAME + " :: onAuthenticationError :: 지문 인증 에러 발생", new String[]{"Error :: " + String.valueOf(errString)});
            // ===============================================================

            /**
             * // -----------------------------------
             * [주요 에러 메시지 정리]
             * // -----------------------------------
             * 1) 시도 횟수가 너무 많습니다. 나중에 다시 시도하세요.
             * // -----------------------------------
             * 2) 사용자가 지문 인식 작업을 취소했습니다.
             * // -----------------------------------
             */

            // [토스트 메시지 표시]
            try { if (this.mContext != null){ Toast.makeText(this.mContext, String.valueOf(errString), Toast.LENGTH_SHORT).show(); } } catch (Exception es){}

            // [인증 결과 처리]
            this.update(String.valueOf(errString), false);
        }


        // [지문 인식 인증 실패 메소드]
        @Override
        public void onAuthenticationFailed() {
            // ===============================================================
            S_Log._E_(ACTIVITY_NAME + " :: onAuthenticationFailed :: 지문 인증 에러 발생", new String[]{"Error :: " + String.valueOf(FAIL_FINGER_AUTH)});
            // ===============================================================

            /**
             * // -----------------------------------
             * [주요 에러 메시지]
             * // -----------------------------------
             * 1) 지문 인증 실패. 다시 시도해주세요 (다른 손가락 지문)
             * // -----------------------------------
             * 2) 지문이 일치하지 않습니다.
             * // -----------------------------------
             */

            // [토스트 메시지 표시]
            try { if (this.mContext != null){ Toast.makeText(this.mContext, String.valueOf(FAIL_FINGER_AUTH), Toast.LENGTH_SHORT).show(); } } catch (Exception es){}

        }


        // [지문 인식 인증 에러 메소드]
        @Override
        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
            // ===============================================================
            S_Log._E_(ACTIVITY_NAME + " :: onAuthenticationHelp :: 지문 인증 실패", new String[]{"Error :: " + String.valueOf(helpString)});
            // ===============================================================

            /**
             * // -----------------------------------
             * [주요 에러 메시지]
             * // -----------------------------------
             * 1) 손가락을 너무 빨리 움직였습니다. 다시 시도해 주세요.
             * // -----------------------------------
             * 2) 지문 센서를 깨끗이 닦고 다시 시도하세요.
             * // -----------------------------------
             */

            // [토스트 메시지 표시]
            try { if (this.mContext != null){ Toast.makeText(this.mContext, String.valueOf(helpString), Toast.LENGTH_SHORT).show(); } } catch (Exception es){}

        }


        // [지문 인식 인증 성공 메소드]
        @Override
        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
            // ===============================================================
            S_Log._W_(ACTIVITY_NAME + " :: onAuthenticationSucceeded :: 지문 인증 성공", null);
            // ===============================================================

            // TODO [인증 결과 처리]
            this.update(SUCCESS_FINGER_AUTH, true);
        }


        // [지문 인식 인증 중도 취소 메소드]
        public void stopFingerAuth(){
            // ===============================================================
            S_Log._E_(ACTIVITY_NAME + " :: stopFingerAuth :: 지문 인증 중도 취소", null);
            // ===============================================================

            try {
                if(cancellationSignal != null && !cancellationSignal.isCanceled()){
                    cancellationSignal.cancel();
                }
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }


        // [지문인식 인증 진행 후 동적 콘텐츠 변경 메소드]
        private void update(String message, boolean success) {
            // ===============================================================
            S_Log._D_(ACTIVITY_NAME + " :: update :: 지문 인증 진행 확인", new String[]{
                    "success :: " + String.valueOf(success),
                    "message :: " + String.valueOf(message)
            });
            // ===============================================================

            if(success == true) { // TODO [지문 인증 성공 한 경우]

                // [콜백 반환]
                if (returnData != null && returnData.isDisposed() == false){
                    returnData.onNext(true);
                    returnData.onComplete();
                    returnData = null;
                }

            }
            else { // TODO [지문 인증 실패 한 경우]

                // [콜백 반환]
                if (returnData != null && returnData.isDisposed() == false){
                    returnData.onError(new Throwable(String.valueOf(message)));
                    returnData.onComplete();
                    returnData = null;
                }

            }

        }

    } // TODO [내부 클래스 종료]


} // TODO [클래스 종료]

 

반응형
Comments