강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

kkkkkk님의 프로필 이미지
kkkkkk

작성한 질문수

스프링 시큐리티 OAuth2

OAuth2AuthorizedClient 이해 및 활용

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님께서 주신 질문에 대한 답변을 드리겠습니다.

  1. Keycloak에서의 실제 세션 로그아웃과 관련된 질문:
    Keycloak에서 로그아웃을 제대로 처리하려면 클라이언트 애플리케이션에서 인증된 사용자의 id_token_hint를 Keycloak에 전송해야합니다. 이 요청은 OpenID Connect의 로그아웃 엔드포인트로 리디렉션됩니다. 요청 URL에 포함된 id_token_hint는 사용자의 현재 세션을 식별하는 데 사용되며, 이 값이 없는 경우 Keycloak은 로그아웃을 처리하지 않을 수 있습니다. OpenId 스코프를 추가해도 id_token을 참조할 수 없는 문제는 특정 환경 설정이나 코드 로직에 따라 달라질 수 있습니다. 아래와 같이 로그아웃 URL을 구성하고 리다이렉트를 시도할 수 있지만, 이를 위해서는 id_token을 정확히 추출하고 사용해야 합니다.

  2. OAuth2AuthorizedClient와 OAuth2AuthenticationToken 사용:
    OAuth2AuthorizedClient는 클라이언트 정보와 토큰 정보를 관리하는 객체입니다. 강의에서 OAuth2User를 통해 OAuth2AuthenticationToken을 생성하고 저장하는 것은 어플리케이션 내에서 사용자를 인증된 상태로 다루기 위함입니다. 이 방식은 Spring Security의 인증 체계에서 사용자를 추적하고 권한 부여를 하는 데 도움을 줍니다. OAuth2User를 생성하고 OAuth2AuthenticationToken에 저장함으로써, 해당 사용자를 인증된 것으로 간주하게 되고, SecurityContextHolder를 통해 인증 상태를 유지할 수 있습니다. 이는 특정한 비즈니스 로직이나 요구사항에 따라 다를 수 있습니다.

Keycloak과 같은 외부 인증 서버를 사용할 때, 로그아웃은 브라우저 세션이나 서버 세션뿐만 아니라 인증 서버와의 세션도 포함하기 때문에 이를 고려한 추가 구현이 필요합니다.

더 많은 정보와 코드 예제는 다음 Spring Boot 문서에서 확인 가능합니다.
Spring Boot OAuth2 Client 설명서

도움이 되셨기를 바랍니다. 더 궁금한 사항이 있으시면 언제든지 질문해 주세요. 감사합니다!

kkkkkk님의 프로필 이미지
kkkkkk

작성한 질문수

질문하기