스프링 부트로 OAuth2 구현(페이스북, 구글, 카카오, 네이버)

* 아래의 모든 코드는 깃 저장소에 올려놨습니다. 참고 부탁드려요 *

OAuth란?

OAuth(Open Authorization)는 토큰 기반의 인증 및 권한을 위한 표준 프로토콜입니다. OAuth와 같은 인증 프로토콜을 통해 유저의 정보를 페이스북, 구글, 카카오 등의 서비스에서 제공받을 수 있고 이 정보를 기반으로 어플리케이션 사용자에게 로그인이나 다른 여러 기능들을 손쉽게 제공할 수 있습니다. 자세한 내용은 여기를 참조하시면 좋을 것 같습니다.

 

스프링 부트로 OAuth2를 통한 로그인 기능

 

스프링 부트로 OAuth2를 통한 로그인 기능을 제공할 수 있습니다. 여기서는 페이스북, 구글, 카카오에서 제공하는 정보를 통해 사용자가 웹페이지에 손쉽게 로그인하는 기능을 구현할 것입니다. 스프링부트로 해당 기능을 구현하기 전에 먼저 위 3가지 서비스의 개발자 사이트에 들어가서 OAuth 인증을 위한 설정을 해야합니다.

 

페이스북

 

1. 페이스북 개발자 페이지에 가서 아래와 같이 새 앱을 추가합니다. 

 

2. 아래와 같이 새 앱에 대한 정보를 입력합니다.

3. 왼쪽 사이드바의 설정 > 기본설정에 보면 앱 ID와 앱 시크릿 코드가 있습니다. OAuth2 인증을 위해 필요한 정보들이며 나중에 스프링 부트의 설정 정보에 저장하여 OAuth2 인증을 하는 데 쓰일 것입니다.

4. 왼쪽 사이드바의 제품에서 Facebook 로그인을 추가합니다. 그 다음 설정에서 다음과 같이 옵션들을 설정합니다. 어플리케이션 개발 시 페이스북에서는 localhost 리디렉션에 대해 개발모드시 자동으로 허용되므로 리디렉션 URI정보에 따로 적지 않아도 됩니다.

 


구글

 

1. 구글 디펠로퍼 콘솔에 접속해서 새 프로젝트를 생성합니다.

2. 프로젝트를 생성할 때 정보를 작성합니다.

 

3. 왼쪽 사이드바의 사용자 인증 정보에서 사용자 인증 정보 만들기 > OAuth2 클라이언트 ID 를 클릭합니다.

 

4. OAuth 클라이언트 ID 만들기 창에서 동의 화면 구성을 클릭합니다.

5. OAuth 동의화면에서 애플리케이션 필요한 정보를 입력한 후 저장합니다.

6. 애플리케이션 유형 중 웹 애플리케이션을 선택한 다음 아래와 같이 입력한 후 저장합니다. 

7. 아래와 같이 OAuth 정보가 설정됬다는 창이 뜨면서 구글 OAuth 로그인 인증을 할 수 있는 준비가 완료됬습니다.


카카오

 

1. 카카오 개발자 페이지에 가서 내 애플리케이션을 클릭한 다음 앱 만들기를 클릭합니다.

2. 앱 만들기 창이 나오면 해당 정보를 입력한 후 앱 만들기를 클릭합니다.

3. 앱을 만든 후 다음과 같이 Key 정보가 주어집니다. 여기서 쓰일 키 정보는 REST API 키입니다.

4. 왼쪽 사이드바의 개요를 클릭하면 다음과 같은 화면이 나타납니다. 사용자 관리에서 사용자 관리를 활성화 한 다음 비즈 앱 정보의 설정을 클릭합니다.

5. 아래와 같이 웹 플랫폼을 추가한 후 사이트 도메인을 아래와 같이 localhost로 저장합니다.

6. 왼쪽 사이드 바에서 설정 > 일반을 클릭한 후 플랫폼에서 위에서 설정했던 웹 플랫폼을 클릭한 뒤 다음과 같이 정보를 저장합니다(8080 포트까지 정확하게 적어야 합니다). 

7. 왼쪽 사이드 바에서 설정 > 고급을 클릭한 후 SecretID를 설정합니다. 

 

8. 아래와 같이 Secret 코드가 생성되는 것을 볼 수 있습니다.

 

네이버

1. https://developers.naver.com/apps 로 가서 내 애플리케이션을 등록합니다. 또한 애플리케이션 사용자의 어떤 정보를 불러올 것인지 설정을 합니다.

 

2. 환경 추가에서 PC 웹을 선택한 다음 서비스 URL과 Callback URL을 다음과 같이 설정합니다. 등록하기를 누르며은 다음 화면으로 이동합니다.

 

 

3. 여기서 Client ID와 Client Secret에 대한 정보를 기억해 둡시다. 스프링 부트 어플리케이션의 설정에 넣어야하는 값들입니다.

 

프로젝트 구조 

\---src
+---main
|   +---java
|   |   \---com
|   |       \---example
|   |           \---oauth2
|   |               |   Oauth2Application.java
|   |               |
|   |               +---security
|   |               |       CustomOAuth2Provider.java
|   |               |       OAuth2Controller.java
|   |               |       SecurityConfig.java
|   |               |       SocialType.java
|   |               |
|   |               \---service
|   |                       CustomOAuth2UserService.java
|   |
|   \---resources
|       |   application.yml
|       |
|       +---static
|       \---templates
|               hello.html
|               home.html
|               login.html
|

 

의존성 

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}

group 'saelobi'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

 

application.yml

# 아래 client-id와 client-secret은 예시로 넣은 더미데이터입니다. 참고만 부탁드려요.
spring:
  h2:
    console:
      enabled: true
      path: /console
  thymeleaf:
    cache: false
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 958180173202-sii4epowero1eouvvjrlrnt27sikub08k.apps.googleusercontent.com
            client-secret: k0asdfag801WdcJF8k9ST_8SG9
          facebook:
            client-id: 11612612521462
            client-secret: 6578235217c5c365a98c51b02d0308ac
custom:
  oauth2:
    kakao:
      client-id: 88a081241234cfacbedef7018ac451316f3
      client-secret: jpXULoEzrasfawegsqYXlRLyyOHn2i60q
    naver:
      client-id: B5yvH3Zaweyawerawwe6repj4z0L
      client-secret: sArXFasdfawef

 

소스 코드

 

Oauth2Application ( main 진입점)

package com.example.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Oauth2Application {
    public static void main(String[] args) {
        SpringApplication.run(Oauth2Application.class, args);
    }
}

 

OAuth2Controller

package com.example.oauth2.security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class OAuth2Controller {

    @GetMapping({"", "/"})
    public String getAuthorizationMessage() {
        return "home";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping({"/loginSuccess", "/hello"})
    public String loginSuccess() {
        return "hello";
    }

    @GetMapping("/loginFailure")
    public String loginFailure() {
        return "loginFailure";
    }
}

 

SecurityConfig

package com.example.oauth2.security;

import com.example.oauth2.service.CustomOAuth2UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.example.oauth2.security.SocialType.*;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                    .antMatchers("/", "/oauth2/**", "/login/**", "/css/**",
                            "/images/**", "/js/**", "/console/**", "/favicon.ico/**")
                    .permitAll()
                    .antMatchers("/facebook").hasAuthority(FACEBOOK.getRoleType())
                    .antMatchers("/google").hasAuthority(GOOGLE.getRoleType())
                    .antMatchers("/kakao").hasAuthority(KAKAO.getRoleType())
                    .antMatchers("/naver").hasAuthority(NAVER.getRoleType())
                    .anyRequest().authenticated()
                .and()
                    .oauth2Login()
                    .userInfoEndpoint().userService(new CustomOAuth2UserService())  // 네이버 USER INFO의 응답을 처리하기 위한 설정
                .and()
                    .defaultSuccessUrl("/loginSuccess")
                    .failureUrl("/loginFailure")
                .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository(
            OAuth2ClientProperties oAuth2ClientProperties,
            @Value("${custom.oauth2.kakao.client-id}") String kakaoClientId,
            @Value("${custom.oauth2.kakao.client-secret}") String kakaoClientSecret,
            @Value("${custom.oauth2.naver.client-id}") String naverClientId,
            @Value("${custom.oauth2.naver.client-secret}") String naverClientSecret) {
        List<ClientRegistration> registrations = oAuth2ClientProperties
                .getRegistration().keySet().stream()
                .map(client -> getRegistration(oAuth2ClientProperties, client))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        registrations.add(CustomOAuth2Provider.KAKAO.getBuilder("kakao")
                    .clientId(kakaoClientId)
                    .clientSecret(kakaoClientSecret)
                    .jwkSetUri("temp")
                    .build());

        registrations.add(CustomOAuth2Provider.NAVER.getBuilder("naver")
                .clientId(naverClientId)
                .clientSecret(naverClientSecret)
                .jwkSetUri("temp")
                .build());
        return new InMemoryClientRegistrationRepository(registrations);
    }

    private ClientRegistration getRegistration(OAuth2ClientProperties clientProperties, String client) {
        if("google".equals(client)) {
            OAuth2ClientProperties.Registration registration = clientProperties.getRegistration().get("google");
            return CommonOAuth2Provider.GOOGLE.getBuilder(client)
                    .clientId(registration.getClientId())
                    .clientSecret(registration.getClientSecret())
                    .scope("email", "profile")
                    .build();
        }

        if("facebook".equals(client)) {
            OAuth2ClientProperties.Registration registration = clientProperties.getRegistration().get("facebook");
            return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
                    .clientId(registration.getClientId())
                    .clientSecret(registration.getClientSecret())
                    .userInfoUri("https://graph.facebook.com/me?fields=id,name,email,link")
                    .scope("email")
                    .build();
        }

        return null;
    }
}
  • OAuth2 인증 설정의 가장 핵심되는 부분입니다. antMatchers 메서드를 이용하여 매칭되는 url를 정의하였습니다. /facebook, /google URL에서는 권한이 있을 때 url에 접근할 수 있도록 설정했습니다.
  • login 성공 시 loginSuccess로 실패시, loginFailure로 redirect되게끔 설정하였습니다. 
  • 만약 권한이 없을 때 나온 error는 /login으로 가게끔 설정하였습니다. authenticationEntryPoint에서 스프링에서 제공하는 form login 템플릿이 아닌 /login 메서드에서 제공하는 템플릿으로 화면에 나타나게끔 지정했습니다.
  • clientRegistrationRepository 메서드를 통하여 facebook, kakao, google의 인증 정보들이 메모리상에 상주하게끔 설정했습니다. 
  • CustomUserOAuth2UserService 클래스를 만들어서 설정에 추가했습니다. NAVER에서는 HTTP response body에 유저 정보에 대한 것을 response 속성 안에 id, user_name 등의 유저 정보를 넣기 때문에 id값을 불러오려면 별도의 처리를 해야되기 때문입니다.

CustomOAuth2UserService

package com.example.oauth2.service;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequestEntityConverter;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    private static final String MISSING_USER_INFO_URI_ERROR_CODE = "missing_user_info_uri";

    private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE = "missing_user_name_attribute";

    private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";

    private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE =
            new ParameterizedTypeReference<Map<String, Object>>() {};

    private Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = new OAuth2UserRequestEntityConverter();

    private RestOperations restOperations;

    public CustomOAuth2UserService() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        this.restOperations = restTemplate;
    }

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        Assert.notNull(userRequest, "userRequest cannot be null");

        if (!StringUtils.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
            OAuth2Error oauth2Error = new OAuth2Error(
                    MISSING_USER_INFO_URI_ERROR_CODE,
                    "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: " +
                            userRequest.getClientRegistration().getRegistrationId(),
                    null
            );
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        }
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();
        if (!StringUtils.hasText(userNameAttributeName)) {
            OAuth2Error oauth2Error = new OAuth2Error(
                    MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
                    "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " +
                            userRequest.getClientRegistration().getRegistrationId(),
                    null
            );
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        }

        RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);

        ResponseEntity<Map<String, Object>> response;
        try {
            response = this.restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE);
        } catch (OAuth2AuthorizationException ex) {
            OAuth2Error oauth2Error = ex.getError();
            StringBuilder errorDetails = new StringBuilder();
            errorDetails.append("Error details: [");
            errorDetails.append("UserInfo Uri: ").append(
                    userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
            errorDetails.append(", Error Code: ").append(oauth2Error.getErrorCode());
            if (oauth2Error.getDescription() != null) {
                errorDetails.append(", Error Description: ").append(oauth2Error.getDescription());
            }
            errorDetails.append("]");
            oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
                    "An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(), null);
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
        } catch (RestClientException ex) {
            OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
                    "An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
        }

        Map<String, Object> userAttributes = getUserAttributes(response);
        Set<GrantedAuthority> authorities = new LinkedHashSet<>();
        authorities.add(new OAuth2UserAuthority(userAttributes));
        OAuth2AccessToken token = userRequest.getAccessToken();
        for (String authority : token.getScopes()) {
            authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
        }

        return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
    }

    // 네이버는 HTTP response body에 response 안에 id 값을 포함한 유저정보를 넣어주므로 유저정보를 빼내기 위한 작업을 함
    private Map<String, Object> getUserAttributes(ResponseEntity<Map<String, Object>> response) {
        Map<String, Object> userAttributes = response.getBody();
        if(userAttributes.containsKey("response")) {
            LinkedHashMap responseData = (LinkedHashMap)userAttributes.get("response");
            userAttributes.putAll(responseData);
            userAttributes.remove("response");
        }
        return userAttributes;
    }
}
  • NAVER의 OAuth2 인증을 통해서 불러온 유저 정보를 처리하기 위한 Custom 클래스입니다. 별도의 설정을 하지 않으면 DefaultOAuth2UserService를 통해 유저 정보를 처리하지만 별도의 처리 로직을 둬야할 경우 이렇게 상속을 통하여 Custom 클래스를 작성해야합니다. 
  • DefaultOAuth2UserService와 달라진 점은 단지 getUserAttributes 메서드를 통해서 별도의 데이터처리를 하는 것 뿐 모든 것은 DefaultOAuth2UserService의 loadUser 메서드와 일치합니다.

 

CustomOAuth2Provider

스프링 부트에서는 google 및 facebook에 대한 OAuth2정보를 기본적으로 제공합니다. 하지만 Kakao와 NAVER 는 스프링 부트에서 기본적인 정보를 제공하지 않으므로 위와 같이 따로 해당 정보를 제공하는 클래스를 작성하여야 합니다.

package com.example.oauth2.security;

import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;

public enum CustomOAuth2Provider {

    KAKAO {
        @Override
        public ClientRegistration.Builder getBuilder(String registrationId) {
            ClientRegistration.Builder builder = getBuilder(registrationId,
                    ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
            builder.scope("profile");
            builder.authorizationUri("https://kauth.kakao.com/oauth/authorize");
            builder.tokenUri("https://kauth.kakao.com/oauth/token");
            builder.userInfoUri("https://kapi.kakao.com/v2/user/me");
            builder.userNameAttributeName("id");
            builder.clientName("Kakao");
            return builder;
        }
    },
    NAVER {
        @Override
        public ClientRegistration.Builder getBuilder(String registrationId) {
            ClientRegistration.Builder builder = getBuilder(registrationId,
                    ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
            builder.scope("profile");
            builder.authorizationUri("https://nid.naver.com/oauth2.0/authorize");
            builder.tokenUri("https://nid.naver.com/oauth2.0/token");
            builder.userInfoUri("https://openapi.naver.com/v1/nid/me");
            builder.userNameAttributeName("id");
            builder.clientName("Naver");
            return builder;
        }
    };

    private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";

    protected final ClientRegistration.Builder getBuilder(
            String registrationId, ClientAuthenticationMethod method, String redirectUri) {
        ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
        builder.clientAuthenticationMethod(method);
        builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
        builder.redirectUriTemplate(redirectUri);
        return builder;
    }

    public abstract ClientRegistration.Builder getBuilder(String registrationId);
}

 

SocialType

package com.example.oauth2.security;

public enum SocialType {
    FACEBOOK("facebook"),
    GOOGLE("google"),
    KAKAO("kakao"),
    NAVER("naver");

    private final String ROLE_PREFIX = "ROLE_";
    private String name;

    SocialType(String name) { this.name = name; }

    public String getRoleType() { return ROLE_PREFIX + name.toUpperCase(); }

    public String getValue() { return name; }

    public boolean isEquals(String authority) { return this.getRoleType().equals(authority);}
}

 

hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>인증을 무사히 완료하였습니다.</h1>
</body>
</html>

 

home.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>홈페이지입니다.</h1><br>
    <a th:href="@{/login}">로그인 하기</a>
</body>
</html>

 

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2>로그인</h2><br/><br/>
    <a href="javascript:;" class="btn_social" data-social="facebook">페이스북 로그인</a><br/>
    <a href="javascript:;" class="btn_social" data-social="google">구글 로그인</a><br/>
    <a href="javascript:;" class="btn_social" data-social="kakao">카카오톡 로그인</a><br/>
    <a href="javascript:;" class="btn_social" data-social="naver">네이버 로그인</a><br/>

    <script>
        let socials = document.getElementsByClassName("btn_social");
        for(let social of socials) {
            social.addEventListener('click', function(){
                let socialType = this.getAttribute('data-social');
                location.href="/oauth2/authorization/" + socialType;
            })
        }
    </script>
</body>
</html>

 

  • 스프링 부트에서 제공하는 OAuth2 클라이언트에서는 기본적으로 OAuth2 권한 요청 url이 /oauth2/authorization/{id}로 지정되어 있기 때문에 위와 같이 자바스크립트로 설정한 모습입니다.

loginFailure.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>인증을 실패하였습니다.</h1>
    <a th:href="@{/login}">다시 로그인하기</a>
</body>
</html>

 

 

테스트

 

 

 

 

이 글을 공유하기

댓글(12)

  • a
    2019.10.17 15:57

    토씨하나 안빠뜨리고 똑같이 입력했는데 아래 에러가 뜨면서 실행이 안됩니다.

    ***************************
    APPLICATION FAILED TO START
    ***************************

    Description:
    Parameter 0 of method clientRegistrationRepository in com.example.oauth2.security.SecurityConfig required a bean of type 'org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties' that could not be found.


    Action:
    Consider defining a bean of type 'org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties' in your configuration.



    (물론 import는 뭔지 안나와있어서 자동완성으로 넣었습니다)


    그리고 application.yml 파일도 목록에 존재하는데 이 파일에는 무엇을 넣는지요?? (안 나와 있어서 여쭙게 되었습니다)

    • 2019.10.17 23:16 신고

      댓글 달아주셔서 감사합니다!

      import 까지 코드에 넣었어야 했는데 제가 꼼꼼하게 못 올렸었네요ㅠ

      로그 내용을 보면 OAuth2ClientProperties 라는 타입인 bean이 스프링 빈 컨테이너에 등록이 되지 않아서 생긴 문제인 것 같습니다.

      자동완성해서 넣은 import 문 중에 잘못 import된 패키지가 있을 가능성이 큽니다. 따라서 해당 bean이 추가되지 않아서 나온 에러인 것 같습니다.

      주말에 급한 포스팅 위주로 리뉴얼할 생각인데 그때 import 문으로 된 코드까지 올리겠습니다! 참고 부탁드려요~

    • alma
      2019.10.23 14:58

      실례지만 글쓴이님 스프링 부트 프로젝트 버전도 같이 알려주실 수 있으신가요?
      저도 2.1.9 버전으로 구현 했는데 댓쓰신 분이랑 같은 에러 나와서 다른 자료 찾아서 구현 했거든요 ㅇㅅㅇ;
      oauth2가 2017년도 이후 부턴가 스프링 부트 2.x 버전 부터 쓰는 방법이 달라졌다 들어서 디펜던시에 추가한 spring starter oauth2 client 일부 기능이 1.x 이후로 지원하지 않는가??? 궁금하네요.

  • 2019.10.24 19:44 신고

    alma : 네 알겠습니다! 조만간 올리겠습니다!!

  • 미스타츄
    2020.01.13 19:05

    alma님 혹시 어떤식으로 해결하셨나요... import한거 하나하나 봤는데 똑같이 했는데도 위에 댓글다신분이랑 같은 에러가 나오네요 ㅠㅠ

    • 2020.03.01 17:55 신고

      안녕하세요! 늦게 답변드려 죄송합니다ㅠ 깃에 코드 올려놨으니 참고 부탁드려요~

  • 부트
    2020.02.13 11:01

    git에 내용이 없던데 혹시 git 올리실 생각은 없으신가요?

    • 2020.03.01 17:54 신고

      안녕하세요! 늦게 답변드려 죄송합니다ㅠ 늦게나마 깃허브에 네이버 로그인 포함해서 코드 올려놨습니다. https://github.com/engkimbs/spring-boot-oauth2

  • 2020.03.21 02:41 신고

    안녕하세요. 포스팅으로 많은 도움을 받았습니다.
    인증까지는 완료된거같은데 이제 받아온 데이터로 db에 저장하려고 하는데요.
    컨트롤러에서 파라미터로 @AuthenticationPrincipal Principal principal을 받으면 principal에 유저정보들이 나오더라구요. 이정보들이 어떻게 저장되는지 알수있을까요?

    • 2020.03.23 22:30 신고

      안녕하세요! 댓글 남겨주셔서 감사합니다~
      스프링은 메서드 안에 있는 Argument에 붙는 @AuthenticationPrincipal 같은 어노테이션의 값을 결정하는 Resolver를 가지고 있습니다. 보통 접미사로 MethodArgumentResolver라는 클래스의 인스턴스가 그역할을 합니다. 자세한 부분은 Controller의 메서드에 breakpoint를 거신 후 stacktrace를 참조해보시면 자세히 알 수 있습니다.

  • hwe
    2020.06.21 12:06

    securityConfig에서 oauth2Login userInfoEndpoint userService 이부분이 안타는데(실행안됨) 이유를 모르겠습니다

  • 2020.08.15 00:45 신고

    정말 깔끔한 튜토리얼입니다. 도움이 많이 되었습니다. 감사합니다.

Designed by JB FACTORY