인프런 커뮤니티 질문&답변
OAuth2AuthorizedClient 이해 및 활용 강의 내용 질문
작성
·
179
·
수정됨
0
1 의문 ................................................
강의 내용중에서
oauth2Login(Customizer.withDefaults())
없이
oauth2Client(Customizer.withDefaults())
만 사용한 경우에는 인가 처리만 하기 때문에 따로 인증 처리를 해주는 과정을 보여주셨는데
2 배경지식 ................................................
oauth2Client 와 oauth2Login 의 차이는
1단계 처리 필터 : OAuth2AuthorizationRequestRedirectFilter 로 동일
2단계 를 처리필터: OAuth2LoginAuthenticationFilter 대신 여기선 OAuth2AuthorizationCodeGrantFilter 사용
즉
OAuth2LoginAuthenticationFilter 를 거치지않아
OAuth2LoginAuthenticationFilter에서 사용하는
OidcAuthorizationCodeAuthenticationProvider에서
IdToken 을 추출해
OidcIdToken를 만들고 OidcUser 를 로드하는 부분의 유무가 핵심적인 차이라고 생각이드는데요
그리고 OAuth2User는 인가 개념만 있지 인증 개념은 없다고 들었습니다
3 질문 .......................................................
단순히 OAuth2User를 만들어 OAuth2AuthenticationToken을 SecurityContextHolder에 수동 저장한다고 해서 "인증된 사용자"로 간주되는 않아서
로그아웃도 제대로 동작하지 않습니다 .
물론 스프링 시큐리티 자체에서 세션,쿠키,SecurityContextHolder에 강제로 저장한 Authentication 없애기를 통해 일부는 처리는 되지만
질문1 : keycloak 에서의 실제 세션은 로그아웃을 못하지않나요 ?
@GetMapping("/logout")
public String logout( HttpServletRequest request, HttpServletResponse response){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 로그아웃 처리
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.logout(request, response, authentication);
// 로그아웃 요청을 위한 Keycloak 로그아웃 URL
String logoutUrl = "http://localhost:8080/realms/oauth2/protocol/openid-connect/logout";
// 로그아웃 후 리디렉션할 URI
String redirectUri = "http://localhost:8081/login"; // 로그아웃 후 돌아올 URL
// 실제 ID Token을 가져오는 코드
// 로그아웃 URL에 필요한 파라미터들을 추가
String logoutRequestUrl = logoutUrl + "?id_token_hint=" + idTokenHint + "&post_logout_redirect_uri=" + redirectUri;
// 로그아웃 요청을 Keycloak으로 리디렉션
return "redirect:" + logoutRequestUrl;
}
이런식으로 강제로 형식 만들려고 해도 mvc 에서 id_token을 참조할 수 있는 방법이없습니다 .
디버깅으로 확인해보니
scope 에 openId 를 추가해서 id_token이 있을거라고 생각했는데
OAuth2AuthorizationCodeGrantFilter의 OAuth2AuthorizationCodeGrantFilter에서
authenticationResult 에
additionalParameters = {HashMap@7785} size = 4
"id_token" -> "ㅁㄴㅇㄹㄴㅇㅁㄻㄴㅇㅁㄴㅇㄹ"
"session_state" -> "7a3b7dbb-ec62-4a07-8ef8-f67b17b2f3ac"
"refresh_expires_in" -> {Integer@7818} 1800
"not-before-policy" -> {Integer@7820} 1746349855
이런식으로 있긴하지만 사용하지않고 authenticationResult로부터
authenticationResult.getClientRegistration() 추출해서 OAuth2AuthorizedClient 만들어서 저장하고 끝내내요
private void processAuthorizationResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request, response);
String registrationId = (String)authorizationRequest.getAttribute("registration_id");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
String redirectUri = UrlUtils.buildFullRequestUrl(request);
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult;
try {
authenticationResult = (OAuth2AuthorizationCodeAuthenticationToken)this.authenticationManager.authenticate(authenticationRequest);
} catch (OAuth2AuthorizationException var16) {
OAuth2AuthorizationException ex = var16;
OAuth2Error error = ex.getError();
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()).queryParam("error", new Object[]{error.getErrorCode()});
if (StringUtils.hasLength(error.getDescription())) {
uriBuilder.queryParam("error_description", new Object[]{error.getDescription()});
}
if (StringUtils.hasLength(error.getUri())) {
uriBuilder.queryParam("error_uri", new Object[]{error.getUri()});
}
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build().encode().toString());
return;
}
Authentication currentAuthentication = this.securityContextHolderStrategy.getContext().getAuthentication();
String principalName = currentAuthentication != null ? currentAuthentication.getName() : "anonymousUser";
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request, response);
String redirectUrl = authorizationRequest.getRedirectUri();
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest != null) {
redirectUrl = savedRequest.getRedirectUrl();
this.requestCache.removeRequest(request, response);
}
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
질문 2 :
강의(OAuth2AuthorizedClient 이해 및 활용)에서는
OAuth2User를 만들어 OAuth2AuthenticationToken 을 저장하는 식으로 처리하셨는데 수업을 듣다 보니 이렇게 처리한 이유가 궁금해 질문 드립니다 .
답변 1
0
안녕하세요, 인프런 AI 인턴입니다.
ckstj8027님께서 주신 질문에 대한 답변을 드리겠습니다.
Keycloak에서의 실제 세션 로그아웃과 관련된 질문:
Keycloak에서 로그아웃을 제대로 처리하려면 클라이언트 애플리케이션에서 인증된 사용자의 id_token_hint를 Keycloak에 전송해야합니다. 이 요청은 OpenID Connect의 로그아웃 엔드포인트로 리디렉션됩니다. 요청 URL에 포함된id_token_hint는 사용자의 현재 세션을 식별하는 데 사용되며, 이 값이 없는 경우 Keycloak은 로그아웃을 처리하지 않을 수 있습니다. OpenId 스코프를 추가해도 id_token을 참조할 수 없는 문제는 특정 환경 설정이나 코드 로직에 따라 달라질 수 있습니다. 아래와 같이 로그아웃 URL을 구성하고 리다이렉트를 시도할 수 있지만, 이를 위해서는 id_token을 정확히 추출하고 사용해야 합니다.OAuth2AuthorizedClient와 OAuth2AuthenticationToken 사용:
OAuth2AuthorizedClient는 클라이언트 정보와 토큰 정보를 관리하는 객체입니다. 강의에서 OAuth2User를 통해 OAuth2AuthenticationToken을 생성하고 저장하는 것은 어플리케이션 내에서 사용자를 인증된 상태로 다루기 위함입니다. 이 방식은 Spring Security의 인증 체계에서 사용자를 추적하고 권한 부여를 하는 데 도움을 줍니다. OAuth2User를 생성하고 OAuth2AuthenticationToken에 저장함으로써, 해당 사용자를 인증된 것으로 간주하게 되고, SecurityContextHolder를 통해 인증 상태를 유지할 수 있습니다. 이는 특정한 비즈니스 로직이나 요구사항에 따라 다를 수 있습니다.
Keycloak과 같은 외부 인증 서버를 사용할 때, 로그아웃은 브라우저 세션이나 서버 세션뿐만 아니라 인증 서버와의 세션도 포함하기 때문에 이를 고려한 추가 구현이 필요합니다.
더 많은 정보와 코드 예제는 다음 Spring Boot 문서에서 확인 가능합니다.
Spring Boot OAuth2 Client 설명서
도움이 되셨기를 바랍니다. 더 궁금한 사항이 있으시면 언제든지 질문해 주세요. 감사합니다!





