본문 바로가기
Project/깃터디 (gitudy)

[깃터디/Auth] 로그인 페이지 요청 구현

by J-rain 2024. 5. 10.

 

 

 

 

💡 .java를 클릭시 관련 커밋으로 이동💡



 

/auth/loginPage

state 생성 → state로 url 생성( 인가 코드 요청하여 URL 빌더로 생성 ) → OAuthService의 loginPage 함수를 통해 로그인 페이지 생성후 리턴 → JsonResult로 리턴

 

AuthController.java

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/auth")
@RestController
public class AuthController {

    private final AuthService authService;
    private final OAuthService oAuthService;
    private final LoginStateService loginStateService;


    @ApiResponse(responseCode = "200", description = "로그인페이지 요청 성공", content = @Content(schema = @Schema(implementation = AuthLoginPageResponse.class)))
    @GetMapping("/loginPage")
    public JsonResult<List<AuthLoginPageResponse>> loginPage() {

        String loginState = loginStateService.generateLoginState();

        // OAuth 사용하여 각 플랫폼의 로그인 페이지 URL을 가져와서 state 주입
        List<AuthLoginPageResponse> loginPages = oAuthService.loginPage(loginState);


        return JsonResult.successOf(loginPages);
    }
}

 

  • loginStateService를 통해 고유한 loginState를 생성한다. 이 state는 CSRF 공격을 방지하는 데 사용되며, OAuth 인증 요청에 포함된다.
  • oAuthService를 사용하여 지원하는 각 OAuth 플랫폼의 로그인 페이지 URL을 가져온다. 이 URL은 각 플랫폼을 통한 인증을 시작할 수 있게 해주며, 각각의 state 값을 포함시킨다.
  • 메소드는 JsonResult 형태로 로그인 페이지 URL 목록을 반환한다. 이 클래스는 API 응답을 표준화하는 데 사용된다.

 

LoginStateService.java

@Slf4j
@Service
@RequiredArgsConstructor
public class LoginStateService {

    private final LoginStateRepository loginStateRepository;

    // Login State 생성
    public String generateLoginState() {
        LoginState loginState = LoginState.builder()
                .build();
        LoginState savedLoginState = loginStateRepository.save(loginState);
        log.info(">>>> [ Login State 생성 ]  {}", savedLoginState);

        return savedLoginState.getState().toString();
    }

    // Login State 검증
    public boolean isValidLoginState(String loginState) {
        UUID uuid;

        // UUID 형식이 아닐 경우 예외처리
        try {
            uuid = UUID.fromString(loginState);
        } catch (IllegalArgumentException e) {
            log.warn(">>>> {} : {}", loginState, ExceptionMessage.LOGINSTATE_INVALID_VALUE);
            throw new LoginStateException(ExceptionMessage.LOGINSTATE_INVALID_VALUE);
        }

        // 객체를 Redis에서 조회후 없는 경우 예외 처리
        LoginState findLoginState = loginStateRepository.findById(uuid)
                .orElseThrow(() -> {
                    log.warn(">>>> {} : {}", loginState, ExceptionMessage.LOGINSTATE_NOT_FOUND);
                    throw new LoginStateException(ExceptionMessage.LOGINSTATE_NOT_FOUND);
                });

        // 사용한 LoginState는 삭제
        loginStateRepository.deleteById(uuid);

        return true;
    }
}

 

  • 로그인 상태 (LoginState) 객체를 생성하고, 이를 Redis에 저장한다. Redis는 빠른 데이터 접근 속도와 함께 만료 기능을 제공하므로, 로그인 상태를 임시 저장하는 용도로 적합한다.
  • 저장된 state는 OAuth 인증 요청에 사용된다.
  • 생성된 state를 반환한다.
  • 주어진 loginState의 유효성을 검증한다. 이는 UUID 형식을 기준으로 검증하며, 형식에 맞지 않는 경우 IllegalArgumentException을 발생시키고, LoginStateException을 통해 오류를 처리한다.
  • Redis에서 state를 조회하여 존재하지 않으면 예외를 발생시키고, 존재한다면 사용 후 삭제한다. 이 과정은 state의 일회성을 보장한다.

 

LoginState.java

@Getter
@ToString
@Builder
@RedisHash(value = "state", timeToLive = 60 * 3) // 3분
public class LoginState {

    @Id
    private String state;  // state 검증
}

 

 

LoginStateRepository.java

public interface LoginStateRepository extends CrudRepository<LoginState, UUID> {
}

 

 

1. 실제 state 생성확인

 

2. state 삭제 주석 추가하여 저장 확인

 

3. test 로직으로 확인

 

4. 삭제 확인

 


테스트

 

LoginStateTest.java

@SpringBootTest
class LoginStateTest {
    @Autowired
    private LoginStateRepository loginStateRepository;

    @AfterEach  // 테스트 후 데이터 삭제
    void tearDown() {
        loginStateRepository.deleteAll();
    }

    @Test
    @DisplayName("LoginState 저장")
    void redisLoginStateSave() {
        // given
        LoginState savedEntity = loginStateRepository.save(LoginState.builder()
                .build());

        // when
        Optional<LoginState> byId = loginStateRepository.findById(UUID.fromString(savedEntity.getState()));

        // then
        UUID stateAsUUID = UUID.fromString(byId.get().getState());
        assertThat(stateAsUUID).isInstanceOf(UUID.class);
    }

    @Test
    @DisplayName("LoginState 삭제")
    void redisLoginStateDelete() {
        // given
        LoginState savedEntity = loginStateRepository.save(LoginState.builder()
                .build());

        loginStateRepository.deleteById(UUID.fromString(savedEntity.getState()));

        // when
        Optional<LoginState> findLoginState = loginStateRepository.findById(UUID.fromString(savedEntity.getState()));

        // then
        assertThat(findLoginState.isEmpty()).isTrue();
    }
}

 

 

 

댓글