1. 개요

Spring Security 5 는 외부 인증 서버를 구성하는 데 사용할 수 있는 새로운 OAuth2LoginConfigurer 클래스를 도입합니다.

이 사용방법(예제)에서는 oauth2Login() 요소 에 사용할 수 있는 다양한 구성 옵션 중 일부를 탐색합니다 .

2. 메이븐 의존성

Spring Boot 프로젝트에서는 starter spring-boot-starter-oauth2-client 를 추가하기만 하면 됩니다 .

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

부트가 아닌 프로젝트에서는 표준 Spring 및 Spring Security 의존성에 추가하여 spring-security-oauth2-clientspring-security-oauth2-jose 의존성을 명시적으로 추가해야 합니다.

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>

3. 클라이언트 설정

Spring Boot 프로젝트에서 구성하려는 각 클라이언트에 대해 몇 가지 표준 속성을 추가하기만 하면 됩니다.

Google 및 Facebook에 인증 공급자로 등록된 클라이언트를 사용하여 로그인을 위한 프로젝트를 설정해 보겠습니다.

3.1. 클라이언트 자격 증명 얻기

Google OAuth2 인증을 위한 클라이언트 자격 증명을 얻으려면 Google API 콘솔 의 "자격 증명" 섹션으로 이동하세요.

여기에서 웹 애플리케이션에 대한 "OAuth2 클라이언트 ID" 유형의 자격 증명을 생성합니다. 결과적으로 Google은 우리를 위해 클라이언트 ID와 비밀을 설정합니다.

또한 사용자가 Google에 성공적으로 로그인한 후 리디렉션되는 경로인 Google 콘솔에서 승인된 리디렉션 URI를 구성해야 합니다.

기본적으로 Spring Boot는 이 리디렉션 URI를 /login/oauth2/code/{registrationId} 로 구성 합니다.

따라서 Google의 경우 다음 URI를 추가합니다.

http://localhost:8081/login/oauth2/code/google

Facebook 인증을 위한 클라이언트 자격 증명을 얻으려면 Facebook for Developers 웹 사이트에 애플리케이션을 등록하고 해당 URI를 "유효한 OAuth 리디렉션 URI"로 설정해야 합니다.

http://localhost:8081/login/oauth2/code/facebook

3.2. Security 구성

다음으로 application.properties 파일에 클라이언트 자격 증명을 추가해야 합니다.

Spring Security 속성에는 spring.security.oauth2.client.registration 이 접두사로 붙고 그 뒤에 클라이언트 이름과 클라이언트 속성 이름이 옵니다 .

spring.security.oauth2.client.registration.google.client-id=<your client id>
spring.security.oauth2.client.registration.google.client-secret=<your client secret>

spring.security.oauth2.client.registration.facebook.client-id=<your client id> 
spring.security.oauth2.client.registration.facebook.client-secret=<your client secret>

최소한 하나의 클라이언트에 대해 이러한 속성을 추가하면 필요한 모든 빈을 설정 하는 Oauth2ClientAutoConfiguration 클래스 가 활성화됩니다.

자동 웹 Security 구성은 간단한 oauth2Login() 요소 를 정의하는 것과 같습니다.

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
         .anyRequest().authenticated()
         .and()
         .oauth2Login();
        return http.build();
    }
}

여기서 우리는 oauth2Login() 요소가 이미 알려진 httpBasic()formLogin() 요소와 유사한 방식으로 사용되는 것을 볼 수 있습니다 .

이제 보호된 URL에 액세스하려고 하면 애플리케이션이 두 개의 클라이언트가 있는 자동 생성된 로그인 페이지를 표시합니다 .

oauth 로그인 기본값

3.3. 다른 클라이언트

Spring Security 프로젝트에는 Google 및 Facebook 외에도 GitHub 및 Okta에 대한 기본 구성도 포함되어 있습니다. 이러한 기본 구성은 인증에 필요한 모든 정보를 제공하므로 클라이언트 자격 증명만 입력할 수 있습니다.

Spring Security에 구성되지 않은 다른 인증 공급자를 사용하려면 권한 부여 URI 및 토큰 URI와 같은 정보를 사용하여 전체 구성을 정의해야 합니다. 다음은 필요한 속성에 대한 아이디어를 얻기 위해 Spring Security의 기본 구성을 살펴보겠습니다.

4. 부트가 아닌 프로젝트에서 설정

4.1. ClientRegistrationRepository Bean 생성

Spring Boot 애플리케이션으로 작업하지 않는 경우 인증 서버가 소유한 클라이언트 정보의 내부 표현을 포함하는 ClientRegistrationRepository을 정의해야 합니다.

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig {
    private static List<String> clients = Arrays.asList("google", "facebook");

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        List<ClientRegistration> registrations = clients.stream()
          .map(c -> getRegistration(c))
          .filter(registration -> registration != null)
          .collect(Collectors.toList());
        
        return new InMemoryClientRegistrationRepository(registrations);
    }
}

여기 에서 ClientRegistration 개체 List이 있는 InMemoryClientRegistrationRepository 를 만듭니다.

4.2. ClientRegistration 개체 빌드

다음 객체를 빌드 하는 getRegistration() 메서드를 살펴보겠습니다 .

private static String CLIENT_PROPERTY_KEY 
  = "spring.security.oauth2.client.registration.";

@Autowired
private Environment env;

private ClientRegistration getRegistration(String client) {
    String clientId = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-id");

    if (clientId == null) {
        return null;
    }

    String clientSecret = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-secret");
 
    if (client.equals("google")) {
        return CommonOAuth2Provider.GOOGLE.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    if (client.equals("facebook")) {
        return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    return null;
}

여기에서는 유사한 application.properties 파일에서 클라이언트 자격 증명을 읽고 있습니다. 그런 다음 Google 및 Facebook 클라이언트의 나머지 클라이언트 속성에 대해 이미 Spring Security에 정의된 CommonOauth2Provider 열거형을 사용합니다.

ClientRegistration 인스턴스는 클라이언트에 해당합니다.

4.3. ClientRegistrationRepository 등록

마지막으로 ClientRegistrationRepository 빈 을 기반으로 OAuth2AuthorizedClientService 빈을 생성하고 둘 다 oauth2Login() 요소 로 등록해야 합니다 .

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated()
      .and()
      .oauth2Login()
      .clientRegistrationRepository(clientRegistrationRepository())
      .authorizedClientService(authorizedClientService());
    return http.build();
}

@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
 
    return new InMemoryOAuth2AuthorizedClientService(
      clientRegistrationRepository());
}

보시다시피 oauth2Login() 의 clientRegistrationRepository() 메서드를 사용 하여 사용자 지정 등록 저장소를 등록할 수 있습니다.

또한 더 이상 자동으로 생성되지 않으므로 사용자 정의 로그인 페이지를 정의해야 합니다. 이에 대한 자세한 내용은 다음 섹션에서 살펴보겠습니다.

로그인 프로세스의 추가 사용자 지정을 계속해 보겠습니다.

5. oauth2Login() 사용자 정의

OAuth 2 프로세스가 사용하고 oauth2Login() 메소드 로 사용자 정의할 수 있는 몇 가지 요소가 있습니다 .

이러한 모든 요소에는 Spring Boot의 기본 구성이 있으며 명시적 구성이 필요하지 않습니다.

구성에서 이를 사용자 정의하는 방법을 살펴보겠습니다.

5.1. 사용자 정의 로그인 페이지

Spring Boot가 우리를 위해 기본 로그인 페이지를 생성하더라도 우리는 일반적으로 우리 자신의 사용자 정의 페이지를 정의하기를 원할 것입니다.

loginPage() 메서드 를 사용하여 oauth2Login() 요소에 대한 새 로그인 URL을 구성하는 것으로 시작하겠습니다 .

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .antMatchers("/oauth_login")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .oauth2Login()
      .loginPage("/oauth_login");
    return http.build();
}

여기에서 로그인 URL을 /oauth_login 으로 설정했습니다 .

다음 으로 이 URL에 매핑되는 메서드 로 LoginController 를 정의해 보겠습니다 .

@Controller
public class LoginController {

    private static String authorizationRequestBaseUri
      = "oauth2/authorization";
    Map<String, String> oauth2AuthenticationUrls
      = new HashMap<>();

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @GetMapping("/oauth_login")
    public String getLoginPage(Model model) {
        // ...

        return "oauth_login";
    }
}

이 메소드는 사용 가능한 클라이언트의 맵과 권한 엔드포인트를 View로 보내야 하며 , 이는 ClientRegistrationRepository에서 얻을 수 있습니다 .

public String getLoginPage(Model model) {
    Iterable<ClientRegistration> clientRegistrations = null;
    ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
      .as(Iterable.class);
    if (type != ResolvableType.NONE && 
      ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
        clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
    }

    clientRegistrations.forEach(registration -> 
      oauth2AuthenticationUrls.put(registration.getClientName(), 
      authorizationRequestBaseUri + "/" + registration.getRegistrationId()));
    model.addAttribute("urls", oauth2AuthenticationUrls);

    return "oauth_login";
}

마지막으로 oauth_login.html 페이지 를 정의해야 합니다 .

<h3>Login with:</h3>
<p th:each="url : ${urls}">
    <a th:text="${url.key}" th:href="${url.value}">Client</a>
</p>

이것은 각 클라이언트에 인증하기 위한 링크를 표시하는 간단한 HTML 페이지입니다.

스타일을 추가한 후 로그인 페이지의 모양을 변경할 수 있습니다.

로그인

5.2. 사용자 지정 인증 성공 및 실패 동작

다른 방법으로 인증 후 동작을 제어할 수 있습니다.

  • 사용자를 지정된 URL로 리디렉션하는 defaultSuccessUrl()failureUrl()
  • 인증 프로세스에 따라 사용자 정의 로직을 실행하기 위한 successHandler()failureHandler()

사용자를 다음으로 리디렉션하도록 Custom URL을 설정하는 방법을 살펴보겠습니다.

.oauth2Login()
  .defaultSuccessUrl("/loginSuccess")
  .failureUrl("/loginFailure");

사용자가 인증 전에 Security 페이지를 방문한 경우 로그인 후 해당 페이지로 리디렉션됩니다. 그렇지 않으면 /loginSuccess 로 리디렉션됩니다 .

사용자가 이전에 Security 페이지에 있었는지 여부에 관계없이 항상 /loginSuccess URL로 보내지기를 원한다면 defaultSuccessUrl(“/loginSuccess”, true) 메서드를 사용할 수 있습니다 .

사용자 정의 핸들러를 사용하려면 AuthenticationSuccessHandler 또는 AuthenticationFailureHandler 인터페이스 를 구현하는 클래스를 만들고 상속된 메서드를 재정의한 다음 successHandler()failureHandler() 메서드 를 사용하여 빈을 설정해야 합니다.

5.3. 사용자 지정 권한 부여 끝점

권한 부여 끝점은 Spring Security가 외부 서버에 대한 권한 부여 요청을 트리거하는 데 사용하는 끝점입니다.

먼저 권한 부여 끝점에 대한 새 속성을 설정하겠습니다 .

.oauth2Login() 
  .authorizationEndpoint()
  .baseUri("/oauth2/authorize-client")
  .authorizationRequestRepository(authorizationRequestRepository());

여기 에서 기본 /oauth2/authorization 대신 baseUri/oauth2/authorize-client 로 수정했습니다 .

또한 다음과 같이 정의해야 하는 authorizationRequestRepository() 빈을 명시적으로 설정하고 있습니다.

@Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> 
  authorizationRequestRepository() {
 
    return new HttpSessionOAuth2AuthorizationRequestRepository();
}

우리는 우리 빈에 대해 Spring 제공 구현을 사용했지만 사용자 정의 구현을 제공할 수도 있습니다.

5.4. 사용자 지정 토큰 끝점

토큰 끝점 은 액세스 토큰을 처리합니다.

기본 응답 클라이언트 구현으로 tokenEndpoint() 를 명시적으로 구성해 보겠습니다 .

.oauth2Login()
  .tokenEndpoint()
  .accessTokenResponseClient(accessTokenResponseClient());

다음은 응답 클라이언트 빈입니다.

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> 
  accessTokenResponseClient() {
 
    return new NimbusAuthorizationCodeTokenResponseClient();
}

이 구성은 기본 구성과 동일하며 공급자와 인증 코드를 교환하는 것을 기반으로 하는 Spring 구현을 사용합니다.

물론 사용자 지정 응답 클라이언트를 대체할 수도 있습니다.

5.5. 사용자 지정 리디렉션 끝점

외부 공급자와의 인증 후 리디렉션할 끝점입니다.

리디렉션 엔드포인트에 대한 baseUri 를 변경하는 방법을 살펴보겠습니다 .

.oauth2Login()
  .redirectionEndpoint()
  .baseUri("/oauth2/redirect")

기본 URI는 login/oauth2/code 입니다.

변경하는 경우 각 ClientRegistration 의 redirectUriTemplate 속성 도 업데이트하고 새 URI를 각 클라이언트에 대한 승인된 리디렉션 URI로 추가해야 합니다.

5.6. 사용자 지정 사용자 정보 끝점

사용자 정보 끝점은 사용자 정보를 얻기 위해 활용할 수 있는 위치입니다.

userInfoEndpoint() 메서드 를 사용하여 이 끝점을 사용자 지정할 수 있습니다 . 이를 위해 userService()customUserType( )과 같은 메서드를 사용 하여 사용자 정보가 검색되는 방식을 수정할 수 있습니다.

6. 사용자 정보 접근

우리가 달성하고자 하는 일반적인 작업은 로그인한 사용자에 대한 정보를 찾는 것입니다. 이를 위해 사용자 정보 엔드포인트에 요청할 수 있습니다.

먼저 현재 사용자 토큰에 해당하는 클라이언트를 가져와야 합니다.

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

@GetMapping("/loginSuccess")
public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) {
    OAuth2AuthorizedClient client = authorizedClientService
      .loadAuthorizedClient(
        authentication.getAuthorizedClientRegistrationId(), 
          authentication.getName());
    //...
    return "loginSuccess";
}

다음으로 클라이언트의 사용자 정보 끝점에 요청을 보내고 userAttributes Map 을 검색합니다 .

String userInfoEndpointUri = client.getClientRegistration()
  .getProviderDetails().getUserInfoEndpoint().getUri();

if (!StringUtils.isEmpty(userInfoEndpointUri)) {
    RestTemplate restTemplate = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken()
      .getTokenValue());
    HttpEntity entity = new HttpEntity("", headers);
    ResponseEntity <map>response = restTemplate
      .exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
    Map userAttributes = response.getBody();
    model.addAttribute("name", userAttributes.get("name"));
}

name 속성을 Model 속성으로 추가하여 사용자에게 환영 메시지로 loginSuccess 보기 에 표시할 수 있습니다 .

환영하다

이름 외에도 userAttributes 맵 에는 email , family_name , picturelocale 과 같은 속성도 포함 됩니다 .

7. 결론

이 기사에서는 Spring Security에서 oauth2Login() 요소를 사용하여 Google 및 Facebook과 같은 다른 공급자와 인증하는 방법을 보았습니다.

또한 이 프로세스를 사용자 지정하는 몇 가지 일반적인 시나리오를 살펴보았습니다.

예제의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .

Security footer banner