본문 바로가기
공부/프로그래밍

[SpringBoot 2.x] (Spring Security) 페이스북 로그인 연동

by demonic_ 2018. 7. 7.
반응형

인터넷에 나와있는 SpringBoot + 페이스북 연동 은 1.x 버전이라 안됩니다.

그래서 Spring Security Oauth2 를 이용해서 로그인하는 방법에 대해 다루를 예정입니다.


시작하기 전에 페이스북 디벨로퍼에 접속하여 새로운 App을 생성합니다.

생성할 때, 'Facebook 로그인' 을 설정해 둡니다.

https://developers.facebook.com/?locale=ko_KR




여기서 사용되는 예제에서는 샘플 앱을 만들었으니 코드를 그대로 사용하셔도 됩니다.



구성은 다음과 같습니다.

- Spring Boot 2.x

- thymeleaf

- Spring security

- Spring security oauth2




1. Gradle 설정

compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')

compileOnly('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.security.oauth:spring-security-oauth2:2.3.3.RELEASE')
compile('org.springframework.security.oauth:spring-security-oauth2')
compile('org.springframework.security:spring-security-oauth2-client')



2. application.yml 설정

프로퍼티 파일을 yml 로 수정한 것입니다.

spring:
  security:
    oauth2:
      client:
        registration:
          # 페이스북 앱 정보
          facebook:
            client-id: 681655205504975
            client-secret: 7a37be5aa42a483202c94744a5747065
          # 구글 앱 정보
          google:
            client-id: test
            client-secret: test_secret

# 소셜 로그인 사용할 SNS 종류. 이번 샘플에는 구글 연동을 하지 않았습니다.
spring.social.auths: facebook,google



3. Spring Security 설정

import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
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.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
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 java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Log
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth_login").permitAll()
                .antMatchers("/**").permitAll()
                .and()
                .oauth2Login()
                .loginPage("/oauth/login.html")
                .clientRegistrationRepository(clientRegistrationRepository())
                .authorizedClientService(authorizedClientService())
                .defaultSuccessUrl("/oauth/login_success")
                .failureUrl("/oauth/login_fail")
            ;
    }

    /**
     * OAuth 로 페이스북 연동 만드는 중.
     * 참조사이트: http://www.baeldung.com/spring-security-5-oauth2-login
     */
    private static String CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration.";

    private static List clients;

    @Value("${spring.social.auths}")
    String socialAuths;

    @Autowired
    private Environment env;

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        clients = Arrays.asList(socialAuths.split(","));
        List registrations = clients.stream()
                .map(c -> getRegistration(c))
                .filter(registration -> registration != null)
                .collect(Collectors.toList());

        return new InMemoryClientRegistrationRepository(registrations);
    }

    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;
    }

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



4. Oauth2 로그인 컨트롤러 생성

LoginOauth2Controller 파일을 생성하고 아래의 코드를 추가합니다.

그리고 샘플이기 때문에 root path("/") 도 추가해 두겠습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.HashMap;
import java.util.Map;

/**
 * 소셜 로그인 담당 컨트롤러
 */
@Controller
public class LoginOauth2Controller {
    @RequestMapping(path={"/", "/main"})
    public String main(){
        return "main";
    }

    // 기본 URL 설정
    private static String authorizationRequestBaseUri = "/oauth2/authorization";
    Map oauth2AuthenticationUrls = new HashMap<>();

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    // 로그인 시도 이후 받아온 정보
    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;

    // 로그인 화면
    @GetMapping("/oauth/login")
    public String login(Model model) {
        Iterable clientRegistrations = null;
        ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
                .as(Iterable.class);
        if (type != ResolvableType.NONE &&
                ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
            clientRegistrations = (Iterable) clientRegistrationRepository;
        }

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

        return "oauth/login";
    }

    // 로그인 성공 후
    @GetMapping("/oauth/login_success")
    public String login_success(Model model, OAuth2AuthenticationToken authentication){
        OAuth2AuthorizedClient client = authorizedClientService
                .loadAuthorizedClient(
                        authentication.getAuthorizedClientRegistrationId(),
                        authentication.getName()
                );

        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 response = restTemplate.exchange(siteUrlCustoom(authentication.getAuthorizedClientRegistrationId(), userInfoEndpointUri), HttpMethod.GET, entity, Map.class);
            Map userAttributes = response.getBody();

            model.addAttribute("userInfo", userAttributes);
        }

        return "oauth/login_success";
    }

    /**
     * 소셜 종류에 따라 URL 구성 변경
     * @param site
     * @return
     */
    protected String siteUrlCustoom(String site, String baseUrl){
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(baseUrl);

        if(site.equals("facebook")){
            uriBuilder.queryParam("fields", "name,email,picture,locale");
        }

        return uriBuilder.toUriString();
    }
}




5. HTML 파일을 생성합니다.

thymeleaf 를 사용하기 때문에 resources/templates 폴더 아래에 두어야 합니다.


5-1. main.html 파일 생성


<!DOCTYPE html>

<html lang="ko">

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="/assets/img/favicon.ico">

    <meta name="description" content="">

    <meta name="author" content="">

</head>

<body>

메인페이지 입니다.

<br>

<a href="/oauth/login">소셜로그인 페이지 이동</a>

</body>

</html>



5-2. oauth/login.html 파일 생성


<!DOCTYPE html>

<html lang="ko">

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="/assets/img/favicon.ico">

    <meta name="description" content="">

    <meta name="author" content="">

</head>

<body>

<p>소셜 로그인 화면</p>

<h3>Login with:</h3>

<p th:each="url : ${urls}">

    <a th:text="${url.key}" th:href="${url.value}">Client</a>

</p>


<!--<br>-->

<!--<p>페이스북 로그인</p>-->

<!--<a href="/oauth2/authorization/facebook" target="_blank">Facebook</a>-->

</body>

</html>



5-3. oauth/login_success.html 파일 생성


<!DOCTYPE html>

<html lang="ko">

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="/assets/img/favicon.ico">

    <meta name="description" content="">

    <meta name="author" content="">

</head>

<body>

<p>소셜 로그인 성공</p>


<p>이름: <span th:text="${userInfo.name}"></span></p>

<p>이메일: <span th:text="${userInfo.email}"></span></p>


<br>

<a href="/">Go Home</a>

</body>

</body>

</html>



실행해서 

http://localhost:8080/ 를 접속하여 링크를 따라가면 됩니다.

구글 버튼은 활성화 되어있지만 작동하지 않습니다.




관련 소스는 깃헙에 올려두었습니다.

https://github.com/lemontia/SpringBoot2_oauth2



반응형

댓글