이슈
OAuth2 로그인 과정에서, authentication을 이용해 jwt를 만들어서 response-header에 전달해주기 위해 AuthenticationSuccessHandler을 상속 받은 클래스를 커스텀해서 successHandler로 등록했다.
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
private final JWTUtil jwtUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
response.addHeader(JWTUtil.AUTHORIZATION_HEADER, JWTUtil.BEARER + jwtUtil.generate(authentication));
}
}
하지만, 로그인 성공 후 홈 화면이 아닌 OAuth2 API에서 지정한 redirect-uri로 이동했다.
이미지를 보면 response-header에 bearer-type의 jwt가 잘 담겨있다. 여기서 의문점이 든 것은, 인증 코드 요청, 인증 토큰 요청, 프로필 정보 요청 순서로 진행해서 jwt를 만들기까지 했는데 왜 다시 코드 요청 uri로 리다이렉트 됐냐는 것이다.
원인
원인을 찾기 위해 OAuth2 Client 라이브러리 없이 카카오 로그인을 해봤다. 유저 정보를 가져오는 코드는 아래와 같다.
@GetMapping("/login/oauth2/code/kakao")
public ResponseEntity<String> getUserInfo(String code) throws JsonProcessingException {
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
//params.add(key, value);
params.add("grant_type", "authorization_code");
params.add("client_id", CLIENT_ID);
params.add("redirect_uri", REDIRECT_URI);
params.add("code", code);
params.add("client_secret", CLIENT_SECRET);
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(params, headers);
ResponseEntity<String> response = rt.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
kakaoTokenRequest,
String.class
);
//response json->object
ObjectMapper objectMapper=new ObjectMapper();
OAuthToken oAuthToken=objectMapper.readValue(response.getBody(),OAuthToken.class);
System.out.println("Kakao access-token: "+ oAuthToken.getAccess_token());
RestTemplate rt2 = new RestTemplate();
HttpHeaders headers2 = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
headers.add("Authorization", "Bearer "+oAuthToken.getAccess_token());
ResponseEntity<String> response2 = rt.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoTokenRequest,
String.class
);
return response2;
}
인증 코드를 제공 받기 위한 redirect-uri 요청 메소드 안에서, 인증 토큰 요청도 하고, 받은 인증 토큰을 가지고 유저 정보 요청도 한다.
라이브러리 없이 직접 카카오 로그인 구현하기 전에는, 코드, 토큰, 유저 정보 요청하는 uri마다 요청 메소드를 각각 구현해야 되는 줄 알았는데, 그럴 필요 없이 redirect-uri 요청 메소드 안에서 코드부터 유저 정보까지 받아올 수 있었다. OAuth2 Client 라이브러리를 사용하면 이 과정을 대신 해주기 때문에 생략 가능하다. 그래서 로그인 성공 후, 인증 코드 요청 uri(redirect-uri)로 이동하는 것이였다.
따라서, 로그인 성공 후 홈 화면으로 리다이렉트 하기 위해서는 핸들러에서 따로 설정해야 한다.
해결
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
private final JWTUtil jwtUtil;
private final ObjectMapper objectMapper;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
protected Log logger = LogFactory.getLog(this.getClass());
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// SecurityContextHolder.getContext().setAuthentication(authentication);'
response.addHeader(JWTUtil.AUTHORIZATION_HEADER, JWTUtil.BEARER + jwtUtil.generate(authentication));
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(final Authentication authentication) {
Map<String, String> roleTargetUrlMap = new HashMap<>();
roleTargetUrlMap.put("ROLE_USER", "/");
roleTargetUrlMap.put("ROLE_ADMIN", "/");
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
String authorityName = grantedAuthority.getAuthority();
if(roleTargetUrlMap.containsKey(authorityName)) {
return roleTargetUrlMap.get(authorityName);
}
}
throw new IllegalStateException();
}
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
RedirectStrategy 클래스를 이용해 지정한 주소로 리다이렉트 되도록 구현했다.
후기
더 쉬운 방법이 있었다. 처음 OAuth2 API에서 인증 코드를 받을 리다이렉트 주소를 지정할 때, 홈 주소로 지정하면 된다. 그래도 해당 이슈 덕분에 라이브러리 없이 OAuth2 로그인도 해보고, OAuth2 로그인 과정을 정확히 이해할 수 있었다.
'Issues' 카테고리의 다른 글
웹브라우저, 웹서버 인코딩 방식 차이 (0) | 2022.02.02 |
---|