스프링 시큐리티 OAuth2/OAuth 2.0 Client - oauth2Client()

OAuth2AuthorizedClient 이해 및 활용

webmaster 2023. 1. 27. 01:26
728x90

OAuth2AuthorizedClient

OAuth2AuthorizedClient

  • OAuth2AuthorizedClient 는 인가받은 클라이언트를 의미하는 클래스다.
  • 최종 사용자(리소스 소유자)가 클라이언트에게 리소스에 접근할 수 있는 권한을 부여하면, 클라이언트를 인가된 클라이언트로 간주한다
  • OAuth2AuthorizedClient 는 AccessToken과 RefreshToken을 ClientRegistration (클라이언트)와 권한을 부여한 최종 사용자인 Principal과 함께 묶어 준다
  • OAuth2AuthorizedClient 의 AccessToken 을 사용해서 리소스 서버의 자원에 접근 할 수 있으며 인가서버와의 통신으로 토큰을 검증할 수 있다
  • OAuth2AuthorizedClient 의 ClientRegistration과 AccessToken을 사용해서 UserInfo 엔드 포인트로 요청할 수 있다

OAuth2AuthorizedClientRepository

OAuth2AuthorizedClientRepository

  • OAuth2AuthorizedClientRepository 는 다른 웹 요청이 와도 동일한 OAuth2AuthorizedClient 를 유지하는 역할을 담당한다.
  • OAuth2AuthorizedClientService 에게 OAuth2AuthorizedClient 의 저장, 조회, 삭제 처리를 위임한다

OAuth2AuthorizedClientService

OAuth2AuthorizedClientService

  • OAuth2AuthorizedClientService 는 애플리케이션 레벨에서 OAuth2AuthorizedClient 를 관리(저장, 조회, 삭제 )하는 일이다.

웹 어플리케이션에서 활용

어플리케이션에서 구조

  • OAuth2AuthorizedClientRepository 나 OAuth2AuthorizedClientService 는 OAuth2AuthorizedClient 에서 OAuth2AccessToken 을 찾을 수 있는 기능을 제공하므로 보호 중인 리소스 요청을 시작할 때 사용할 수 있다

OAuth2AuthorizationCodeGrantFilter

OAuth2AuthorizationCodeGrantFilter

  • Authorization Code Grant 방식으로 권한 부여 요청을 지원하는 필터
  • 인가서버로부터 리다이렉트 되면서 전달된 code를 인가서버의 Access Token으로으로 교환한다.
  • OAuth2AuthorizedClientRepository를 사용하여 OAuth2AuthorizedClient 저장 후 클라이언트의 Redirect Uri로 이동한다
  • 해당 필터는 실제 최종 로그인까지의 처리까지 진행하는 것이 아닌 인가 서버까지만 인증을 진행한다.
  • 실행 조건
    • 요청 파라미터에 code와state 값이 존재하는지 확인
    • OAuth2AuthorizationRequest 객체가 존재하는지 확인
    • 위 두 조건이 참이 되는 경우는 Client에서 인증 서버에 1단계 요청(Code)을 정상적으로 완료를 했을 때이다.

Test

HomeController

@Controller
public class HomeController {
  @GetMapping("/home")
  public String home() {
    return "home";
  }
}

home.html

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  <script>
    function authorizationCode(){
      window.location = new URL('http://localhost:8081/oauth2/authorization/keycloak');
    }

    function password(){
      window.location = new URL('http://localhost:8081/oauth2/authorization/keycloak2');
    }

    function clientCredentials(){
      window.location = new URL('http://localhost:8081/oauth2/authorization/keycloak3');
    }


  </script>
</head>
<body>
<div>Welcome</div>
<form sec:authorize="isAnonymous()" action="#">
  <p><input type="button" onclick="authorizationCode()" value="AuthorizationCode Grant" />
  <p><input type="button" onclick="password()" value="Resource Owner Password Grant" />
  <p><input type="button" onclick="clientCredentials()" value="Client Credentials Grant" />
</form>
</body>
</html>

OAuth2ClientConfig

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http.authorizeHttpRequests(
          authRequest -> authRequest.antMatchers("/home", "/client").permitAll().anyRequest().authenticated())
      .oauth2Client(Customizer.withDefaults()); 
  http.logout().logoutSuccessUrl("/home");
  return http.build();
}

ClientController

@Controller
public class ClientController {

  @Autowired
  private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;

  @Autowired
  private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;

  @GetMapping("/client")
  public String client(HttpServletRequest request, Model model) {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    String clientRegistrationId = "keycloak";
    OAuth2AuthorizedClient oAuth2AuthorizedClient1 = oAuth2AuthorizedClientRepository.loadAuthorizedClient(
        clientRegistrationId, authentication, request);

    OAuth2AuthorizedClient oAuth2AuthorizedClient2 = oAuth2AuthorizedClientService.loadAuthorizedClient(
        clientRegistrationId, authentication.getName());
    OAuth2AccessToken accessToken = oAuth2AuthorizedClient1.getAccessToken();

    OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
    OAuth2User oAuth2User = oAuth2UserService.loadUser(
        new OAuth2UserRequest(oAuth2AuthorizedClient1.getClientRegistration(), accessToken));
    OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(oAuth2User,
        Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")),
        oAuth2AuthorizedClient1.getClientRegistration().getRegistrationId());

    SecurityContextHolder.getContext().setAuthentication(authenticationToken); //시큐리티 컨텍스트에 인증 객체 담기
    model.addAttribute("accessToken", accessToken.getTokenValue());
    model.addAttribute("refreshToken", oAuth2AuthorizedClient1.getRefreshToken().getTokenValue());
    model.addAttribute("principalName", oAuth2User.getName());
    model.addAttribute("clientName", oAuth2AuthorizedClient1.getClientRegistration().getClientName());
    return "client";
  }
}

Client.html

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head>
  <meta charset="UTF-8">
  <title>Insert title here</title>

</head>
<body>
<div>Welcome</div><p></p>
<div sec:authorize="isAuthenticated()"><a th:href="@{/logout}">Logout</a></div><br>
<div sec:authorize="isAuthenticated()">principalName: <span th:text="${principalName}">인가받은 클라이언트</span></div><br>
<div sec:authorize="isAuthenticated()">clientName: <span th:text="${clientName}">인가받은 클라이언트</span></div><br>
<div sec:authorize="isAuthenticated()">accessToken: <span th:text="${accessToken}">인가받은 클라이언트</span></div><br>
<div sec:authorize="isAuthenticated()">refreshToken: <span th:text="${refreshToken}">인가받은 클라이언트</span></div><br>
</body>
</html>
Footer
  • OAuth2AuthorizedClientRepository, OAuth2AuthorizedClientService 를 통해서 client 인증을 할 수 있으며 해당 애플리케이션 같은 경우는 실제 로그인 처리가 된 것이 아닌 익명사용자로 인증이 된 것이다(client-인증서버 인증은 성공)
728x90