SpringBoot OAuth2.0 소셜로그인 처리

devdo·2022년 6월 12일
1

SpringBoot

목록 보기
25/35
post-thumbnail

OAuth2.0을 사용하는 이유

만약 로그인을 직접 구현하면 다음을 전부 구현해야 한다.

  • 로그인시 보안
  • 비밀번호 찾기
  • 비밀번호 변경
  • 회원정보 변경
  • 회원가입 시 이메일 혹은 전화번호 인증
    1

    1) 로그인시 보안, 회원가입시 이메일 or 전화번호 인증 절차 비밀번호 찾기, 변경 회원정보 변경 등
    이 번거로운 절차 없이 SNS 계정만 있으면 바로 로그인할 수 있게 할 수 있다.
    2) OAuth 로그인 구현시 앞선 목록의 것들을 모두 구글, 네이버 등에 맡기면 되니 서비스 개발에 집중할 수 있다!

스프링부트1.5에서 OAuth2.0 연동방식이 많이 달라졌다.
스프링 부트 2.0에서는 Spring Security Oatuh2 Clinet 라이브러리 사용.


Oauth2 흐름도

accessToken 을 얻을 수 있다.

client
Resource Server
Resource Owner

client id
client secret
redirect URL


build.gradle 추가

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: '2.5.4'

구글 서비스 등록

먼저 구글 서비스에 신규 서비스를 생성, 여기서 만들어질 발급된 인증정보는 다음 3가지이다.

  • clientId : 클라이언트 ID
  • clientSecret : 클라이언트 보안 비밀번호
  • redirect URL

구글 클라우드 플랫폼 주소(google cloud platform)로 이동

1) 프로젝트 생성

2) API 및 서비스 대시보드 이동
사용자 인증 정보 > 사용자 인증 정보 만들기

3) OAuth 클라이언트 ID 만들기 > 동의 화면 구성 버튼 클릭

4) OAuth 동의 화면 입력

5) OAuth 클라이언트 ID 만들기

6) 승인된 리다이렉션 URL

  • 승인된 리다이렉션 URL은 서비스에서 파라미터로 인증 정보를 주었을 때 인증시 성공하면 구글에서 리다이렉트할 URL임.
  • 스프링 부트 2 버전의 시큐리티에서는 기본적으로 {도메인}/login/oauth2/code/{소셜서비스코드}로 리다이렉트 URL을 지원하고 있음.
  • 사용자가 별도로 리다이렉트 URL을 지원하는 Controller를 만들 필요가 없음. 시큐리티에서 이미 구현해 놓은 상태임.
  • 현재는 개발 단계이므로 http://localhost:8080/login/oauth2/code/google 로만 등록하자.
  • AWS 서버에 배포하게 되면 localhost 말고 추가로 주소를 추가해야하며, 이건 추후 단계에서 진행하면 됨.

구현 소스

설정

application.yml 등록

  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 클라이언트 ID
            client-secret: 클라이언트 보안 비밀번호
            scope:
            - email
            - profile

Security Config 추가

	.oauth2Login()	
	.loginPage("/loginForm") 
	.userInfoEndpoint()
	.userService(principalOauthUserService)
  • oauth2Login()
    : OAuth 2 로그인 기능에 대한 여러 설정의 진입점

  • userInfoEndpoint()
    : OAuth 2 로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당

  • userService()
    : 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록, DefaultOAuth2UserService를 상속한 커스터마이징한 OAuth2UserService를 등록해야 한다.


구현 소스

SecurityConfig

private final OAuth2UserService oAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/todo/**").permitAll()
                .antMatchers("/users/**").permitAll()
                .anyRequest().authenticated();   // 그외는 인증을 해야 한다.
                
            http.oauth2Login()
                    .userInfoEndpoint()
                    .userService(oAuth2UserService);
    }

UserController

    // http://localhost:8080/users/oauth 을 입력을 해야 json으로 토큰을 볼 수 있어
    @GetMapping("/oauth")
    public OAuth2AuthenticationToken oauthToken(OAuth2AuthenticationToken token) {
        return token;
    }

OAuth2UserInfo

public interface OAuth2UserInfo {
    String getProviderId();

    String getProvider();

    String getEmail();

    String getName();
}

FacebookUserInfo

@Data
public class FacebookUserInfo implements OAuth2UserInfo {
    private Map<String, Object> attributes; // oauth2User.getAttributes()

    public FacebookUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("id");
    }

    @Override
    public String getProvider() {
        return "facebook";
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }
}

GoogleUserInfo

@Data
public class GoogleUserInfo implements OAuth2UserInfo{

    private Map<String, Object> attributes; // oauth2User.getAttributes()

    public GoogleUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("sub");
    }

    @Override
    public String getProvider() {
        return "google";
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }
}

OAuth2UserService

@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        log.info("userRequest: {}", userRequest);

        ClientRegistration clientRegistration = userRequest.getClientRegistration();
        log.info("clientRegistration :{}", clientRegistration);
        OAuth2User oAuth2User = super.loadUser(userRequest);

        oAuth2User.getAuthorities().forEach((k) -> {
            log.info("k: {}", k);
        });

        // oauth 회원가입 강제 등록
        OAuth2UserInfo oAuth2UserInfo = null;

        if (clientRegistration.getRegistrationId().equals("google")) {
            log.info("구글 로그인 요청");
            oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
            log.info("oAuth2UserInfo: {}",oAuth2UserInfo);
        } else if(clientRegistration.getRegistrationId().equals("facebook")){
            log.info("페이스북 로그인 요청");
            oAuth2UserInfo = new FacebookUserInfo(oAuth2User.getAttributes());
            log.info("oAuth2UserInfo: {}",oAuth2UserInfo);
        }else{
            log.info("우리는 구글과 페이스북만 지원합니다.");
        }

        String email = oAuth2UserInfo.getEmail();
        String name = oAuth2UserInfo.getName();
        log.info("email: {}", email);
        log.info("name: {}", name);
        Optional<User> optionalUser = userRepository.findByEmail(email);

        User user = null;
        if (optionalUser.isPresent()) {
            log.info("로그인을 이미 했음, 자동회원가입이 되어있다.");
        } else {
            user = User.builder()
                    .name(name)
                    .email(email)
                    // password ecode 처리는 controller에 처리하는 게 나을 것 같음.
                    .password("githere")
                    .build();

            userRepository.save(user);
//            return new PrincipalDetails(user, oAuth2User.getAttributes());    // TODO: NPE 발생

        }
        return oAuth2User;
    }
}

OAuth2UserRequest 정보

  • registrationId/clientName : 현재 로그인 진행 중인 서비스를 구분하는 코드 ex) 'Google'

  • Attributes : OAuth2UserService를 통해 가져온 OAuth2User의 attribute를 담을 클래스

이런 정보로 반환을 하면 인증이 된 것이다.



출처

profile
배운 것을 기록합니다.

0개의 댓글