본문 바로가기
관리자

Programming-[Backend]/Keycloak

Keycloak ID(username) 찾기 기능 구현하기

728x90
반응형

1. ID 찾기 기능

필요한 경우가 있을 수 있어 추가적인 SPI(Service Provider Interface)로 개발할 수 있다.

다만, ID 찾기 기능은 적용하기에 좀 애매한 부분이 있다. 보안적인 측면에서 문제가 없다고 판단되고 안전하다고 생각될 경우에만 적용해야한다.

  • ID까지 찾을 수 있다면, 악의적인 사용자가 ID, PW 모두 찾아서 계정을 탈취할 수 있다.
  • 글로벌 서비스(Google, Figma 등)에서는 제공하지 않는다.

따라서 간이로만 작성하였으며, form template과 연동하는 원리를 이해하는 목적으로 작성했다. 추가적인 구현은 필요하다면 추가로 하면 될 것 같다.

소스코드는 아래 링크의 forgot-username-extension 부분에서 확인할 수 있다.

https://github.com/thomasdarimont/keycloak-extension-playground

 

 
 

이메일을 username과 함께 사용하는 경우, 이메일을 몰라서 ID 찾기를 했는데 이메일로 보내주는게 역설적이다. 핸드폰 번호 등을 강제로 받아 핸드폰으로 응답을 전송해줄 수도 있겠으나, 핸드폰 번호를 받는 것은 유저에게나, 법률 사항을 지켜야하는 서비스에게나 모두 부담이 될 수 있다.


2. 인증 코드 구현

 

2.1 action 메서드

정책에 따라 사용자에게 받을 정보를 결정해야한다. fullName, dateOfBirth를 받아서 이메일로 보낸다고 가정한다. 이때 keycloak상의 유저 프로필에 fullName, dateOfBirth 값이 있어야만 한다.

아래와 같이 action 코드를 변경한다. 간이로 lookupUsername이라는 메서드를 만들어 이름을 검증한다고 해보자.

public void action(AuthenticationFlowContext context) {

  MultivaluedMap<String, String> formParams = context.getHttpRequest().getDecodedFormParameters();

  UsernameLookupRequest lookupRequest = new UsernameLookupRequest();
  lookupRequest.setFullName(formParams.getFirst("fullName"));
  lookupRequest.setDateOfBirth(formParams.getFirst("dataOfBirth"));

  UsernameLookupResponse lookupResponse = usernameLookupService.lookupUsername(lookupRequest);

  String positiveMessage = "입력하신 정보와 일치하는 사용자가 존재하는 경우, 곧 계정에 등록된 이메일 주소로 사용자 이름이 발송됩니다.";

  String message = lookupResponse.getErrorCode() == null ? positiveMessage : lookupResponse.getErrorCode();
  context.resetFlow();

  UriBuilder loginUriBuilder = UriBuilder.fromUri(Urls.realmLoginPage(context.getUriInfo().getBaseUri(), context.getRealm().getName()))
      .queryParam(Constants.CLIENT_ID, context.getAuthenticationSession().getClient().getClientId())
      .queryParam(Constants.TAB_ID, context.getAuthenticationSession().getTabId());
  Response response = context.form()
      .setAttribute("lookupResultMessage", message)
      .setAttribute("loginUrl", loginUriBuilder.build())
      .createForm("forgot-username-result.ftl");

  context.challenge(response);
}

 

2.2 UsernameLookupService

public class UsernameLookupService {

  public UsernameLookupResponse lookupUsername(UsernameLookupRequest request) {
    
    //request를 받아와서, fullName과 dateOfBirth를 통해 유저를 검증하고, Email을 발송한다. 
    
    UsernameLookupResponse response = new UsernameLookupResponse();
    response.setErrorCode(null);
    return response;
  }

  @Data
  public static class UsernameLookupRequest {

    String fullName;

    String dateOfBirth;
  }

  @Data
  public static class UsernameLookupResponse {

    String errorCode;

    String username;
  }
}

위와 같이 간이 코드를 작성해보았다. request를 받아와서 검증하고, 검증된 경우 email을 발송하면 된다. 이메일 발송은 AWS SNS Service 등 서비스를 추가해주면 된다. 자세한 내용은 아래 링크를 참고한다.

https://whitepro.tistory.com/1023

 

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

문자 및 이메일로 본인 인증을 하는 flow를 추가하는 Provider를 생성하고, 사용하는 방법에 대해 다룹니다. 1. 소스 코드keycloak Expert인 dasniko라는 분의 SMS 2차 인증 authenticator 예시 코드https://github.co

whitepro.tistory.com

 

 


 

3. flow 구성 및 템플릿 파일 작성

 

3.1 forgot-username-form.ftl

fullName, dataOfBirth 값만 받도록 처리한다.

<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-u2f-login-form" method="post">

  <div class="${properties.kcFormGroupClass!}">
    <label for="fullName" class="${properties.kcLabelClass!}">full name</label>
    <input id="fullName" type="text" name="fullName" class="${properties.kcInputClass!}" />
  </div>

  <div class="${properties.kcFormGroupClass!}">
    <label for="dateOfBirth" class="${properties.kcLabelClass!}">Date of Birth</label>
    <input id="dateOfBirth" type="date" name="dateOfBirth" class="${properties.kcInputClass!}" />
  </div>

  <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}"
         type="submit" value="${msg("doSubmit")}"/>

</form>

 

3.2 login.ftl

 

해당 파일은 keycloak의 특정 테마를 위해 커스터마이징하는 login.ftl 파일을 의미한다. 여기에 아래와 같이 요소를 추가하고, Authenticator에서 검증하는 ‘fu’ 값이 ‘true’가 되도록 추가 파라미터를 전달하도록 한다. 다시 말해 ID 찾기 버튼을 누르면 로그인 페이지 url 뒤에 “?fu=true” 가 전달되도록 한다.

  <script>
//중략
document.addEventListener("DOMContentLoaded", function() {
      const currentUrl = new URL(window.location.href);  // 현재 URL 가져오기
      currentUrl.searchParams.set("fu", "true");  // 'fu=true' 쿼리 파라미터 추가

      const forgotUsernameLink = document.querySelector(".forgot-username-button");
      if (forgotUsernameLink) {
        forgotUsernameLink.href = currentUrl.toString();  // 새 URL 설정
      }
    });
  </script>
<#elseif section = "form">
    // 기존 코드들 생략

    <!-- Username 찾기 링크 추가 -->
      <div class="forgot-username-link">
        <a href="#" class="forgot-username-button">ID 찾기</a>
      </div>

  </div>

 

3.3 flow 추가

 

browser form flow상에서 로그인을 할려는 유저가 ID 찾기 버튼을 눌렀을 때 ForgotUsernameAuthenticator가 실행되어야한다. browser form flow의 내부에 Required로 처리해놓는다.

 

그리고 반드시 설정 버튼을 눌러서 alias, URL Parameter가 제대로 설정되어있는지 확인한다. getConfig() 메서드 때문에 config 값을 불러와야하기 때문이다.

 
 

flow 핵심

Required로 설정되어있어서 form flow상 무조건 바로 authenticate 메서드가 실행된다. 그러나 authenticate의 메서드상 요청 URL에 fu=true라는 파라미터가 있어야만 실행되도록 해놨기 때문에 ID 찾기 버튼을 눌렀을 때만 해당 메서드가 실행된다.

728x90
반응형