minlog
article thumbnail

시큐리티 소셜 로그인 처리 - Google oauth2

 
사용자가 간편하게 로그인 할 수 있도록 설정해주는 소셜 로그인 기능을 추가해보려고한다. 
먼저  google api 서비스를 이용해서 프로젝트를 등록하고 사용자 고유 아이디와 비밀번호를 받아야한다.
 
 

01 ) Google api 신청

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

1. 프로젝트 등록

 
 
2. OAuth 클라이언트 아이디 만들기 
-  'OAuth 클라이언트 ID' 를 선택 시 먼저 OAuth 동의를 해주어야한다. 

 
- 동의 후 다시  'OAuth 클라이언트 ID' 선택

  • 애플리케이션 유형 : 웹 애플리케이션
  • 승인된 리다이랙션 URL : http://localhost:8089/login/oauth2/code/google

중요 한 위의 두가지를 입력하면 구글 API 서비스에서의 설정은 끝난다.
생성 후 출력되는 클라이언트 아이디와 보안 비밀번호는 Code에 추가해야하기 때문에 따로 저장해두는 것이 좋다.
 

 


 

02 ) 프로젝트 내 설정

 
1. 빌드설정
 
프로젝트에 소셜 로그인을 적용하기 위해 build.gradle 에 'spring-boot-starter-oauth2-client'를 추가해주어야한다.
 
📑 build.gradle 

    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'//구글소셜로그인

 
 
 
2. Properties 설정
 
기존 applicaiton.properties 파일에 추가해도 되고,
따로 관리하는 properties 파일을 생성하여 내용을 추가해주어도 된다.
 
내부에 들어가는 내용은 구글에서 발급받은 OAuth 클라이언트 ID/PW이다.
 


📑 applicaiton-oauth.properties 

spring.security.oauth2.client.registration.google.client-id=631790768947-l20nog5jnte2i3m6qf13p1fd63ioum23.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.registration.google.scope=email

 
위 처럼 파일을 따로 생성해주었을 경우 기존 파일에도  추가된 파일이 동작 할 수 있도록 설정을 해주어야한다.
 


📑 applicaiton.properties

spring.profiles.include=oauth

 
 


 

03 ) 로그인 화면에 소셜로그인 버튼 생성

 

 


📑 SecurityConfig
 

SecurityConfig파일에  oauth2Login을 추가해주면 된다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    
    ...
    
    http.oauth2Login(); //소셜 로그인 - 구글 추가

    return http.build();
}

 

소셜 로그인시 - 기본 확인 가능한 사용자 정보

 
 
 


 

04 ) 소셜 로그인 로직

  • 회원 Service : 소셜 로그인 후, 프로젝트 내 회원처리와 성공 결과 정보를 반환 해주는 서비스 클래스
  • 회원 DTO : 소셜 로그인 시 반환되는 객체타입이 기존 회원의 타입과 다르기 때문에 일치시켜주어야한다. 

 
 
 

OAuth2User  ·  OAuth2UserService  ·  DefaultOAuth2UserService

소셜로그인으로 로그인한 사용자 정보는 OAuth2User 타입 객체로 출력된다.
-
OAuth2UserService는 일반 로그인에서 사용되었던,
UserDetailsService의 OAuth 버전이다.
-
DefaultOAuth2UserServiceOAuth2UserService를 상속받아 구현한 클래스로
원하는 기능을 편리하게 사용이 가능하다.

 
 
 
 


📑ClubOAuth2UserDetailService
: 소셜 로그인 후, 프로젝트 내 회원처리와 성공 결과 정보를 반환 해주는 서비스 클래스
 

1. DefaultOAuth2UserService 상속 

@Service
public class ClubOAuth2UserDetailService extends DefaultOAuth2UserService {
	...
}

 
2. 회원 처리를 하기 위해,  loadUser  메서드 오버라이드

  • 회원가입 처리를 위한 repository final 선언
  • 받아오는 값 userRequest 에 들어 있는 속성 값은 구글 프로젝트 생성시 설정한 'API 설정 범위' 와 연관이 있다.
  • Map<String, Object> 형태로 사용할 수 있다.
    • 범위 값 : sub,pricture,email,email_verified
package com.example.springsecurity.security.service;


import com.example.springsecurity.entity.ClubMember;
import com.example.springsecurity.entity.ClubMemberRole;
import com.example.springsecurity.repository.ClubMemberRepository;
import com.example.springsecurity.security.dto.ClubAuthMemberDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
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 java.util.Optional;
import java.util.stream.Collectors;

@Log4j2
@Service
@RequiredArgsConstructor
public class ClubOAuth2UserDetailService extends DefaultOAuth2UserService {

    private final ClubMemberRepository repository;
    private final PasswordEncoder passwordEncoder;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("oauth2 login--------------------");
        log.info("userRequest : {}" , userRequest);

        String clientName = userRequest.getClientRegistration().getClientName();
        log.info("clientName : {}",clientName);
        log.info("clientName : {}",userRequest.getAdditionalParameters());

        OAuth2User oAuth2User = super.loadUser(userRequest);

        log.info("=======================================");
        log.info("어떤 값을 가지고 있는지 출력 ");
        oAuth2User.getAttributes().forEach((k,v)->{
            log.info(k+":"+v);
            //sub,picture, email,email_verified  정보가 추출된다.
        });
        log.info("=======================================");

        // 이메일 추출
        String email = null;
        //구글 로그인일 경우
        if (clientName.equals("Google")){
            email = oAuth2User.getAttribute("email");
        }
        log.info("email : {}",email);

        //회원가입 기능
        ClubMember member = saveSocialMember(email);

        //가입 또는 로그인 완료된 회원 정보 출력
        ClubAuthMemberDTO clubAuthMemberDTO = new ClubAuthMemberDTO(
          member.getEmail(),
          member.getPassword(),
          true,
          member.getRoleSet().stream().map(
                  role -> new SimpleGrantedAuthority("ROLE_"+role.name())
          ).collect(Collectors.toSet()),
          oAuth2User.getAttributes()
        );

        clubAuthMemberDTO.setName(member.getName());

        return clubAuthMemberDTO;
    }
    
    // 회원가입 로직
	private ClubMember saveSocialMember(String email) {
        // 이미 있는 회원인지 확인
        Optional<ClubMember> result = repository.findByEmail(email, true);

        // 이미 존재한다면
        if (result.isPresent()){
            return result.get();
        }

        // 존재하지 않는 회원이라면, 패스워드는 기본 1111, social은 true로 설정하여 회원가입
        ClubMember clubMember = ClubMember.builder().email(email)
                .name(email)
                .password("1111")
                .fromSocial(true)
                .build();
        clubMember.addMemberRole(ClubMemberRole.USER); // 권한 기본 User

        ClubMember save = repository.save(clubMember);

        return save;
    }
  
}

 
 


📑ClubAuthMemberDTO
: 소셜 로그인 시 반환되는 객체타입이 기존 회원의 타입과 다르기 때문에 일치

 

 

3. 소셜가입 정보 내용 추가
 

  • OAuth2User 인터페이스를 상속
public class ClubAuthMemberDTO extends User implements OAuth2User {

 

  • OAuth2User 에서 정보를 가져오는 getAttributes()메서드 오버라이드
//OAuth2User 정보
@Override
public Map<String, Object> getAttributes() {
    return this.attr;
}

 

  • OAuth2User 정보를 받아주는 필드 추가
private Map<String,Object> attr;

 

  • OAuth2User 정보가 포함된 생성자 추가
 //OAuth2User 정보를 바꿔주는 생성자
public ClubAuthMemberDTO(
                          String username,
                          String password,
                          Boolean fromSocial,
                          Collection<? extends GrantedAuthority> authorities,
                          Map<String,Object> attr //OAuth2User 정보
                          ) {
    this(username,password,fromSocial,authorities); // 기존생성자
    this.attr = attr; //OAuth2User 정보
}

 


 

05 ) 소셜 로그인 성공 후 페이지 이동

 


📑ClubLoginSuccessHandler

 
1. AuthenticationSuccessHandler를 상속 받아 성공 후 로직 추가

  • DefaultRedirectStrategy: 소셜 로그인시 다른 링크로 이동시키기 위한 객체로 내부적으로 sendRedirect를 통해 화면을 전환한다.
package com.example.springsecurity.security.handler;

...

@Log4j2
public class ClubLoginSuccessHandler implements AuthenticationSuccessHandler {



    // 소셜 로그인시에는 다른 링크로 보내주기위해 사용.
    DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();


    //login 성공 후 로직
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
    throws IOException, ServletException {
        log.info("----------------------------");
        log.info("소셜 로그인 성공");

        ClubAuthMemberDTO authMemberDTO = (ClubAuthMemberDTO)authentication.getPrincipal();
        boolean fromSocial = authMemberDTO.getFromSocial();


        //boolean passwordResult = passwordEncoder.matches("1111", authMemberDTO.getPassword());
        boolean passwordEquals = authMemberDTO.getPassword().equals("1111");

        log.info(authMemberDTO.getPassword());

        log.info("Need Modify Member ? {} ", fromSocial);
        log.info("passwordResult ? {} ", passwordEquals);


        if (fromSocial && passwordEquals){
            log.info("소셜확인 - 회원정보 변경 가능 페이지로 이동");
            redirectStrategy.sendRedirect(request,response,"/sample/modify?from=social");
        }

    }
}

 


📑SampleController

2. 회원정보 수정페이지 이동

...
@PreAuthorize("hasRole('USER')")
@GetMapping("/modify")
public void memberModify(){
    log.info("USER page");
}

 
 

profile

minlog

@jimin-log

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!