목차

구글, 네이버 OAuth2 로그인 구현

DTO 구현

CustomOAuth2User

OAuth2Dto

GoogleDto

NaverDto

Repository 추가

MembersRepository

Service 구현

CustomOAuth2UserService

SecurityConfig 수정

프론트 페이지 추가

로그인 페이지

로그인 버튼 가이드

메인 컨트롤러 수정

프로젝트 구조

GitHub 링크

실행화면

구글, 네이버 OAuth2 로그인 구현

DTO 구현

CustomOAuth2User 구현

dto 패키지를 생성 후 CustomOAuth2User 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.dto

CustomOAuth2User.java

package com.example.oauth2.dto;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

import com.example.oauth2.entity.Members;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class CustomOAuth2User implements OAuth2User {

    private final Members members;

    @Override
    public Map<String, Object> getAttributes() {
        return null;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return members.getRole();
            }
        });

        return collection;
    }

    @Override
    public String getName() {
        return members.getUserid();
    }

}

OAuth2Dto 구현

OAuth2Dto 인터페이스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.dto

OAuth2Dto.java

package com.example.oauth2.dto;

public interface OAuth2Dto {

    String getUserid(); // Spring Security username
    
    String getUsername();
    
    String getEmail();
            
    String getProvider();

    String getProviderId();
    
}

GoogleDto 구현

GoogleDto 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.dto

GoogleDto.java

package com.example.oauth2.dto;

import java.util.Map;

public class GoogleDto implements OAuth2Dto {

    private final String provider = "google";
    private final Map<String, Object> attribute;
    
    public GoogleDto(Map<String, Object> attribute) {
        this.attribute = attribute;
    }

    @Override
    public String getUserid() {
        return provider + "_" + attribute.get("sub").toString();
    }
    
    @Override
    public String getUsername() {
        return attribute.get("name").toString();
    }

    @Override
    public String getEmail() {
        return attribute.get("email").toString();
    }
    
    @Override
    public String getProvider() {
        return provider;
    }
    
    @Override
    public String getProviderId() {
        return attribute.get("sub").toString();
    }

}

NaverDto 구현

NaverDto 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.dto

NaverDto.java

package com.example.oauth2.dto;

import java.util.Map;

public class NaverDto implements OAuth2Dto {

    private final String provider = "naver";
    private final Map<String, Object> attribute;
    
    public NaverDto(Map<String, Object> attribute) {
        this.attribute = (Map<String, Object>) attribute.get("response");
    }

    @Override
    public String getUserid() {
        return provider + "_" + attribute.get("id").toString();
    }
    
    @Override
    public String getUsername() {
        return attribute.get("name").toString();
    }

    @Override
    public String getEmail() {
        return attribute.get("email").toString();
    }
    
    @Override
    public String getProvider() {
        return provider;
    }
    
    @Override
    public String getProviderId() {
        return attribute.get("id").toString();
    }

}

Repository 추가

repository 패키지를 생성 후 MembersRepository 인터페이스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.repository

MembersRepository.java

package com.example.oauth2.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
import com.example.oauth2.entity.Members;
 
public interface MembersRepository extends JpaRepository<Members, Integer> {
 
    // 회원 정보 조회
    Members findByUserid(String userid);
 
}

Service 추가

CustomOAuth2UserService 구현

service 패키지를 생성 후 CustomOAuth2UserService 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.service

OAuth2User oAuth2User = super.loadUser(userRequest); 공급자의 리턴 값

구글 : Name: [11147028552], Granted Authorities: [[OAUTH2_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]], User Attributes: [{sub=11147028552, name=홍길동, given_name=홍길동, picture=https://lh3.googleusercontent.com/a/ACg8ocK5gzFsvqesu2TuOLIWlBYmlIMNHFPpkhT, email=test@gmail.com, email_verified=true}]

네이버 : Name: [{id=ERShm-qmOBe2Z-vMqvkYU9EXU-DVH email=test@naver.com, name=홍길동}], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{resultcode=00, message=success, response={id=ERShm-qmOBe2Z-vMqvkYU9EXU-DVH, email=test@naver.com, name=홍길동}}]

CustomOAuth2UserService.java

package com.example.oauth2.service;

import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import com.example.oauth2.dto.CustomOAuth2User;
import com.example.oauth2.dto.GoogleDto;
import com.example.oauth2.dto.NaverDto;
import com.example.oauth2.dto.OAuth2Dto;
import com.example.oauth2.entity.Members;
import com.example.oauth2.repository.MembersRepository;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final MembersRepository membersRepository;
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        System.out.println("=============================");
        System.out.println("oAuth2User : " + oAuth2User);
        System.out.println("=============================");

        // 공급자
        String registrationId   = userRequest.getClientRegistration().getRegistrationId();
        
        OAuth2Dto oAuth2Dto     = null;
        
        if (registrationId.equals("google")) {
            oAuth2Dto   = new GoogleDto(oAuth2User.getAttributes());
        }
        else if (registrationId.equals("naver")) {
            oAuth2Dto   = new NaverDto(oAuth2User.getAttributes());
        }
        else {
            return null;
        }

        // 회원 정보 조회
        Members memberInfo = membersRepository.findByUserid(oAuth2Dto.getUserid());

        if (memberInfo == null) {
            memberInfo = Members.builder()
                    .userid(oAuth2Dto.getUserid())
                    .email(oAuth2Dto.getEmail())
                    .username(oAuth2Dto.getUsername())
                    .role("ROLE_USER")
                    .provider(oAuth2Dto.getProvider())
                    .providerId(oAuth2Dto.getProviderId())
                    .build();

            membersRepository.save(memberInfo); // 회원가입
        }

        return new CustomOAuth2User(memberInfo);

    }

}

SecurityConfig 수정

변경된 내용 : Custom oauth2Login, 로그아웃 추가

SecurityConfig.java

package com.example.oauth2.config;

import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
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.web.SecurityFilterChain;

import com.example.oauth2.service.CustomOAuth2UserService;

import lombok.RequiredArgsConstructor;
 
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    private final CustomOAuth2UserService customOAuth2UserService;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        // csrf token
        http
            .csrf((csrf) -> csrf.disable());  // 비활성화
        
        // 인가
        http
            .authorizeHttpRequests((authorize) -> authorize
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()   // resources의 static(정적파일) 모두 허용
                    .requestMatchers("/", "/login", "/login/oauth2/code/*").permitAll()  // 보안 검사 없이 접근을 허용
                    //.requestMatchers("/member/**").hasAnyRole("ADMIN", "USER")  // role - ADMIN, USER만 접근 허용
                    //.requestMatchers("/admin").hasRole("ADMIN")  // role - ADMIN만 접근 허용
                    .anyRequest().authenticated()
            );
        
        // Custom oauth2Login
        http
            .oauth2Login((oauth2) -> oauth2
                .loginPage("/login")
                .userInfoEndpoint((userInfoEndpointConfig) ->
                        userInfoEndpointConfig.userService(customOAuth2UserService))
            );
        
        // 로그아웃
        http
            .logout((logout) -> logout.logoutUrl("/logout")
                    .logoutSuccessUrl("/")
            );
 
        return http.build();
    }
  
}

프론트 페이지 추가

로그인 컨트롤러 추가

LoginController 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.controller

LoginController.java

package com.example.oauth2.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
 
@Controller
public class LoginController {
 
    /**
     * 로그인 페이지
     * @return
     */
    @GetMapping("/login")
    public String login() {
        return "login";
    }

}

로그인 페이지 추가

templates에 login.mustache 파일을 생성합니다.

login.mustache

<!DOCTYPE html>
<html lang="ko"> 
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>로그인</title>
        <style>
            .container {
                width: 800px !important ;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-12 pt-5">
                    <div class="card mb-4">
                        <h5 class="card-header text-center">로그인</h5>
                        <div class="card-body">
                            <div class="row">
                                <div class="text-center">
                                    <a href="/oauth2/authorization/google"><img src="/images/button/google_login.png" alt="google"></a>  
                                    <a href="/oauth2/authorization/naver"><img src="/images/button/naver_login.png" width="40px" height="40px" alt="naver"></a>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-12 text-center">
                    <a href="/" class="btn btn-outline-secondary">취소</a>
                </div>
            </div>
        </div>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    </body>
</html>

로그인 버튼 가이드

구글 버튼 가이드 링크: https://developers.google.com/identity/branding-guidelines?hl=ko

네이버 버튼 가이드 링크: https://developers.naver.com/docs/login/bi/bi.md

이미지는 static에 넣어주시면 됩니다. ex) static/images/button/google_login.png

버튼 이미지 없으신 분은 아래 이미지 활용하세요.

google naver

메인 컨트롤러 수정

변경된 내용 : 로그인 여부 체크, 로그인 사용자 정보 가져오는 로직 추가

MainController.java

package com.example.oauth2.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.example.oauth2.dto.CustomOAuth2User;

@Controller
public class MainController {

    /**
     * 메인 페이지
     * @param customOAuth2User
     * @param model
     * @return
     */
    @GetMapping("/")
    public String index(@AuthenticationPrincipal CustomOAuth2User customOAuth2User, Model model) {
        
        // 로그인 여부 체크
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        
        if (!(principal instanceof String)) {
            // 로그인 아이디
            String id = SecurityContextHolder.getContext().getAuthentication().getName();
            System.out.println("========================");
            System.out.println("아이디 : " + id);
            System.out.println("========================");
            
            // Membars 정보
            System.out.println("========================");
            System.out.println("아이디 : " + customOAuth2User.getMembers().getUserid());
            System.out.println("이메일 : " + customOAuth2User.getMembers().getEmail());
            System.out.println("이름 : " + customOAuth2User.getMembers().getUsername());
            System.out.println("권한 : " + customOAuth2User.getMembers().getRole());
            System.out.println("========================");
            
            model.addAttribute("isLogin", true);
            model.addAttribute("userid", customOAuth2User.getMembers().getUserid());
            model.addAttribute("username", customOAuth2User.getMembers().getUsername());
        }
        else {
            model.addAttribute("isLogin", false);
        }
        
        return "index";
    }

}

프로젝트 구조

참고사진_1

GitHub 링크

전체 소스 다운로드

실행화면

참고사진_2

©2024, DevDream