Programming-[Backend]/Keycloak

keycloak 2차 인증(문자, 이메일) 구현하기 w/ AWS SNS

컴퓨터 탐험가 찰리 2024. 10. 21. 09:53
728x90
반응형

문자 및 이메일로 본인 인증을 하는 flow를 추가하는 Provider를 생성하고, 사용하는 방법에 대해 다룹니다.


 

1. 소스 코드

keycloak Expert인 dasniko라는 분의 SMS 2차 인증 authenticator 예시 코드

https://github.com/dasniko/keycloak-2fa-sms-authenticator

함께 올려져있는 영상을 보면 대부분 이해할 수 있음

 

 


2. 코드 설명 및 변경

2.1 SmsAuthenticator.class

  • action 메서드에서 사용자가 입력한 Code 값과 내부에서 지정한 Code 값을 비교. 성공과 실패에 따라 context.success(), context.failuareChallenge 등을 호출하여 인증 단계를 진행하고, 사용자에게 보여줄 페이지(createform 메서드, .ftl 파일)도 설정함
  • authenticate 메서드에서 인증 과정에 사용되는 TTL, 테마, 설정 값 등을 호출한다. 이후 context.challenge() 메서드를 통해 인증을 진행

아래와 같은 사용자 속성값을 조회하는 코드가 있기 때문에, 소스코드의 mobile_number를 phoneNumber로 변경하는 경우, 실제 사용자의 속성 값에 phoneNumber가 있어야한다.

String phoneNumber = user.getFirstAttribute(PHONE_NUMBER_FIELD);

2.2 SmsAuthenticatorFactory.class

Authenticator를 만드는 Factory 클래스

  • getConfigProperties()
@Override
public List<ProviderConfigProperty> getConfigProperties() {
    return List.of(
       new ProviderConfigProperty(SmsConstants.CODE_LENGTH, "Code length", "The number of digits of the generated code.", ProviderConfigProperty.STRING_TYPE, 6),
       new ProviderConfigProperty(SmsConstants.CODE_TTL, "Time-to-live", "The time to live in seconds for the code to be valid.", ProviderConfigProperty.STRING_TYPE, "300"),
       new ProviderConfigProperty(SmsConstants.SENDER_ID, "SenderId", "The sender ID is displayed as the message sender on the receiving device.", ProviderConfigProperty.STRING_TYPE, "Keycloak"),
       new ProviderConfigProperty(SmsConstants.SIMULATION_MODE, "Simulation mode", "In simulation mode, the SMS won't be sent, but printed to the server logs", ProviderConfigProperty.BOOLEAN_TYPE, true),
       new ProviderConfigProperty(SmsConstants.TOPIC_ARN, "Topic ARN", "토픽용 ARN", ProviderConfigProperty.STRING_TYPE, "")
    );
}

Authenticator에 주입할 설정 값들을 의미. 여기에 들어간 값을 Authenticator 클래스에서 context.getAuthenticatorConfig();를 통해 각 값들을 불러와서 활용 가능. 위 코드에서는 TOPIC_ARN 값을 추가하여 AWS의 발송 서비스(SNS, Simple Notification Service)에서 사용할 토픽의 ARN 값을 넣고 참조할 수 있도록 추가하는 예시 설정값을 넣음

  • getRequirementChoices()

여기서 리턴하는 값들이 해당 flow를 keycloak admin에서 설정할 때 flow의 type이 된다. REQUIRED, CONDITIONAL, DISABLED 등이 있으며 아래 keycloak 세팅 부분에서 UI와 함께 살펴보면 이해된다.

  • getDisplayType()

여기서 설정하는 이름 값이 keycloak admin에서 flow를 설정할 때 나타나는 이름이 된다. 이 부분도 keycloak 세팅 부분에서 살펴본다.

 

2.3 AwsSmsService.class

SmsService 인터페이스를 상속. AWS SNS를 사용할려면 senderID가 필요하기 때문에 config에서 이 값을 가져옴. SMSType도 Transactional로 설정

이후 AWS에서 제공하는 SDK의 sns.publish 메서드를 사용하여 문자를 발송함

 

2.4 AwsSmsEmailService.class(커스텀 클래스)

AWS SNS에서는 문자 발송 서비스 뿐만 아니라 Email 발송 서비스도 제공함. 이를 구현하기 위해 커스텀하게 제작한 클래스

import java.util.Map;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.PublishRequest;

public class AwsSmsEmailService implements SmsService {

    private static final SnsClient sns = SnsClient.builder()
       .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
          "YOUR_AWS_ACCESS_TOKEN",
          "YOUR_AWS_SECRET_TOKEN"
       )))
       .region(Region.AP_NORTHEAST_2)
       .build();

    private final String topicArn;

    AwsSmsEmailService(Map<String, String> config) {
       this.topicArn = config.get("topicArn");
    }

    @Override
    public void send(String phoneNumber, String message) {

       PublishRequest request = PublishRequest.builder()
          .topicArn(topicArn)
          .subject("테스트 이메일 입니다")
          .message(message)
          .build();

       sns.publish(request);
    }

}

 

AWS에서 보안 자격 증명에서 IAM Key를 만들고, access token 및 secret token 값을 입력. topicArn은 AWS SNS 서비스에서 생성한 주제(topic)의 Arn 주소를 입력하여 설정하면 된다. config에서 불러오는 값이므로 AuthenticatorFactory 부분에서 설정하면 됨

 

이후 maven의 package task를 실행하여 .jar 파일로 만들어준다

./mvnw package

./target/ 디렉토리에서 생성된 .jar 파일을 도커 이미지의 /opt/keycloak/providers/ 디렉토리 내에 넣어준다.

 


 

3. keycloak 세팅

 

 

browser flow에 적용하는 방법을 통해 세팅

기본으로 등록된 flow는 수정할 수 없으므로 duplicate하여 copy된 flow를 사용해야함

 

Add Step - SMS Authentication flow를 추가

 

 

일반 로그인을 의미하는 username and password form login flow 아래에 SMS Authentication flow를 드래그하여 배치한다(계층은 들여쓰기로 구분됨)

 

Requirement를 선택할 수 있는 부분이 있는데, 이 부분이 위에서 설정한 getRequirementChoices() 메서드에서 지정한 부분들이 나오는 부분이다. Requied로 설정해주면 먼저 설정된 Username Password Form이 완료된 후 다음 단계로 필수적으로 실행된다.

세팅을 눌러 아래와 같이 Arn 등의 값을 줄 수 있다. Alias 값을 설정하여 이름을 지정해주고 저장해주어야만 AuthenticatorFactory에서 설정하는 config 객체가 설정되므로 반드시 저장해준다.

 

 

이메일 방식으로 전송하는 경우 topicARN을 참고하도록 해두었으므로 Topic ARN도 기입한다.


4. AWS 세팅

4-1. 이메일 세팅

이메일을 활용하여 인증 코드를 보내고 싶은 경우, AWS SNS(Simple Notification Service)를 사용하면 된다. 아래와 같이 주제 및 구독을 생성하고 수신자 이메일을 등록하면 된다.

 

4-2. 문자 세팅

문자를 활용하여 인증 코드를 보내고 싶은 경우, 서울 리전(ap-northeast-2)에서는 불가능하다. 가장 가까운 도쿄 리전(ap-northeast-1)으로 이동하여 테스트해볼 수 있다.

여기서도 토픽과 구독을 생성하면 된다. 프로토콜은 SMS로, 엔드 포인트는 +82로 시작하는 국제 전화번호 기준 양식으로 입력하여 등록해주면 된다.

 


5. 실습

5.1 문자 인증

위에서 설명한 내용을 바탕으로 아래 두 가지 사항을 미리 세팅해두면 된다.

  • Authenticator 방식을 문자 인증 방식으로 세팅하고 서버를 실행
  • admin에서 Authentication 중, browser flow에서 SMS Authentication flow를 추가한다.

 

이후 로그인 시, 위 그림대로 code를 입력하는 화면이 나온다. 또한 AWS에 등록해둔 전화번호로 국제 발신 문자가 오는 것을 확인할 수 있다(스팸 차단이 되어있을 수도 있으므로 스팸 차단 앱을 확인).

 

 

5.2 이메일 인증

다음과 같이 준비한다.

  • SmsServiceFactory에서 SimulationMode가 아닐 때 리턴하는 SmsService 객체를 AwsSmsEmailService로 변경
@Slf4j
public class SmsServiceFactory {

    public static SmsService get(Map<String, String> config) {
       if (Boolean.parseBoolean(config.getOrDefault(SmsConstants.SIMULATION_MODE, "false"))) {
          return (phoneNumber, message) ->
             log.warn(String.format("***** SIMULATION MODE ***** Would send SMS to %s with text: %s", phoneNumber, message));
       } else {
          return new AwsSmsEmailService(config); // 이 부분
       }
    }

}
  • SmsAuthenticator - authenticate 메서드에서 SmsServiceFactory.get을 통해 SmsService 객체를 AwsSmsEmailService 객체로 들고올 텐데, 공통된 send 메서드의 mobileNumber 인자를 받는 것을 볼 수 있다. 다만 AwsSmsEmailService의 send 메서드 구현체는 이 인자 값을 사용하지 않으므로 참고만 한다. 만약 공통된 인터페이스를 원한다면 업데이트해주면 된다.
SmsServiceFactory.get(config.getConfig()).send(mobileNumber, smsText);
//mobileNumber를 받지만 실제로는 사용하지 않음
  • 다시 SmsAuthenticator용 .jar 파일을 패키징하고, 서버에 넣어주고 서버를 재실행한다. 이미지가 캐싱되어있을 수 있으므로 실행 중인 컨테이너들을 삭제하고(가능하다면 이미지까지 검색해서 삭제), 아래 명령어들로 캐시없이 재실행한다.
$docker-compose build --no-cache
$docker-compose up -d
  • 똑같이 admin에서 SMS Authentication flow를 추가되어 있음을 확인한다.

사용자의 이메일 및 Topic ARN이 제대로 설정되어있는지 검토 후 로그인을 시도해본다. keycloak의 서버에 등록된 user의 이메일 주소와 AWS의 구독에 등록한 엔드포인트 이메일 주소가 일치되어야함에 유의한다.

아래처럼 이메일이 오는 것을 볼 수 있다.

728x90
반응형