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

@RegisteredOAuth2AuthorizedClient 이해 및 활용

webmaster 2023. 2. 24. 00:10
728x90

@RegisteredOAuth2AuthorizedClient
구조

  • 파라미터를 OAuth2AuthorizedClient 타입 인자로 리졸브 해준다
  • OAuth2AuthorizedClientArgumentResolver 에서 요청을 가로채어 유형별로 권한 부여 흐름을 실행하도록 한다
    • 이 방법은 OAuth2AuthorizedClientManager 나 OAuth2AuthorizedClientService 로 OAuth2AuthorizedClient 에 접근하는 것보다 편리하다
  • @RegisteredOAuth2AuthorizedClient를 선언하고 OAuth2AuthorizedClient 인자로 지정한 메서드이면 OAuth2AuthorizedClientArgumentResolver 가 실행되면서 OAuth2AuthorizedClientManager 통해 인가 처리한다
  • token endpoint로 값을 얻어 오는 것까지, OAuth2AuthorizedClientArgumentResolver가대신해주지만 커스텀한 설정이 존재한다면 해당 리졸버를 사용 못한다.

Test

@Controller
public class LoginController {

  @Autowired
  private DefaultOAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;

  @Autowired
  private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;

  private Duration clockSkew = Duration.ofSeconds(3600);

  private Clock clock = Clock.systemUTC();

  @GetMapping("/v2/oauth2Login")
  public String oauth2LoginV2(
      @RegisteredOAuth2AuthorizedClient("keycloak") OAuth2AuthorizedClient authorizedClient,
      Model model) {
    if (authorizedClient != null) {
      OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService();
      ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
      OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
      OAuth2UserRequest oAuth2UserRequest = new OAuth2UserRequest(clientRegistration, accessToken);
      OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);

      SimpleAuthorityMapper authorityMapper = new SimpleAuthorityMapper();
      authorityMapper.setPrefix("SYSTEM_");
      Set<GrantedAuthority> grantedAuthorities = authorityMapper.mapAuthorities(
          oAuth2User.getAuthorities());

      OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken(
          oAuth2User, grantedAuthorities, clientRegistration.getRegistrationId());

      SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken);

      model.addAttribute("oAuth2AuthenticationToken", oAuth2AuthenticationToken);
      model.addAttribute("AccessToken", authorizedClient.getAccessToken().getTokenValue());
      model.addAttribute("RefreshToken", authorizedClient.getRefreshToken().getTokenValue());
    }
    return "home";
  }

  private boolean hasTokenExpired(OAuth2Token token) {
    return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
  }

  @GetMapping("/logout")
  public String logout(Authentication authentication, HttpServletRequest request,
      HttpServletResponse response) {
    SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
    logoutHandler.logout(request, response, authentication);
    return "redirect:/";
  }
}
  • @RegisteredOAuth2AuthorizedClient을 사용한 것만으로 OAuth2AuthorizedClientArgumentResolver가 동작한다.
  • Token을 얻기 위해 이전에 직접 코드에 작성한 것을 OAuth2AuthorizedClientArgumentResolver가 대신해 준다.
  • authorizedClient에 OAuth2AuthorizedClientArgumentResolver가 값을 넣어준 것을 확인할 수 있다.

index.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');
    }

  </script>
</head>
<body>
<div>Welcome</div>
<form sec:authorize="isAnonymous()" action="#">
  <p><input type="button" onclick="authorizationCode()" value="AuthorizationCode Grant" /></p>
  <p><div sec:authorize="isAnonymous()"><a th:href="@{/oauth2Login(username='user',password='1234')}">Password Flow Login</a></div></p>
  <p><div sec:authorize="isAnonymous()"><a th:href="@{/v2/oauth2Login(username='user',password='1234')}">Password Flow Login V2</a></div></p>
  <div sec:authorize="isAnonymous()"><a th:href="@{/oauth2Login}">Client Credentials Flow Login</a></div>
</form>
</body>
</html>
728x90