Spring Security :: Oauth Session(1)

hyunjoon park·2024년 2월 11일
1

Spring Security

목록 보기
6/12
post-thumbnail

Spring Boot + Security로 Oauth 공부하기

build.gradle

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-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

SecurityConfig 파일설정하기

package com.example.oauth.global.config.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(
        auth -> auth
          .requestMatchers("/", "/oauth2/**", "/login/**").permitAll()
          .anyRequest().authenticated()
      )
      .csrf(csrf -> csrf.disable())
      .formLogin(login -> login.disable())
      .httpBasic(basic -> basic.disable())
      .oauth2Login(Customizer.withDefaults());

    return http.build();
  }
}

application.yml

server:
  servlet:
    context-path: /api

spring:
  jpa:
    hibernate:
      ddl-auto: create
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/${DB_NAME}?serverTimezone=UTC&characterEncoding=UTF-8
    username: ${DB_USERNAME]
    password: ${DB_PASSWORD}

  main:
    allow-bean-definition-overriding: true

  # Google Oauth 설정
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${CLIENT_ID}
            client-secret: ${CLIENT_SECRET}
            redirect-uri: http://localhost:8080/login/oauth2/code/google
            authorization-grant-type: authorization_code
            scope:
              - profile
              - email

Oauth2UserService 응답받기

service 폴더 안에 CustomOauth2UserService 클래스를 만들고 DefaultOAuth2UserService 를 상속받는다

package com.example.oauth.domain.auth.service;

import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.stereotype.Service;

@Service
public class CustomOauth2UserService extends DefaultOAuth2UserService {

}

Oauth2User 타입의 loadUser 메서드를 구현해보자

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

}

이 메서드는 OAuth2UserRequest를 request 받으며, 예외가 발생하면 OAuth2AuthenticationException 에러가 발생한다


loadUser() 구현하기

package com.example.oauth.domain.auth.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;

@Service
public class CustomOauth2UserService extends DefaultOAuth2UserService {
  @Override
  public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {
    OAuth2User oAuth2User = super.loadUser(request);
    System.out.println("oAuth2User = " + oAuth2User.getAttributes());

    String registrationId = request.getClientRegistration().getRegistrationId();

    if(registrationId.equals("google")) {

    }
    
    // 만약 카카오나 네이버 Oauth를 사용한다면
    // if(registrationId.equals("naver") {
    //
    // }

    // if(registrationId.equals("kakao") {
    //
    // }
  }
}

super 키워드를 이용해 request 받은 값의 attributes를 출력해보고
request 받은 값의 registrationId를 oauth 종류별로(google, kakao, naver) 처리 로직을 작성한다

종류별로 분리하는 이유는 각각 응답하는 형태가 다르기 때문이다
추후 알아보도록 하자


Oauth2ResponseDto

package com.example.oauth.domain.auth.presentation.dto.response;

public interface OAuth2Response {
  String getProvider();
  String getProviderId();
  String getEmail();
  String getName();
}

다음과 같이 getProvider(), getProviderId(), getEmail(), getName() 4개의 메서드를 정의하여 놓고 naver, google, kakao 등 각각의 dto 클래스를 만들어서 implements 해서 사용하겠다(여기서는 google만 사용할 것이니까 google dto를 만들어서 상속받아 쓰겠다)


GoogleResponseDto

package com.example.oauth.domain.auth.presentation.dto.response.google;

import com.example.oauth.domain.auth.presentation.dto.response.OAuth2Response;

import java.util.Map;

public class GoogleResponse implements OAuth2Response {

  private final Map<String, Object> attribute;

    public GoogleResponse(Map<String, Object> attribute) {
        this.attribute = attribute;
    }

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

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

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

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

OAuth2Response 를 implements 해주고
json을 읽기 위해 Map<String, Object> 타입으로 attribute라는 필드를 만들어준다

각각의 메서드에서 attribute에서 get() 를 활용하여 값을 가져온다


CustomOauth2UserService

@Service
public class CustomOauth2UserService extends DefaultOAuth2UserService {
  @Override
  public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {
    OAuth2User oAuth2User = super.loadUser(request);
    System.out.println("oAuth2User = " + oAuth2User.getAttributes());

    String registrationId = request.getClientRegistration().getRegistrationId();

    OAuth2Response oAuth2Response = null;

    if(registrationId.equals("google")) {
      oAuth2Response = new GoogleResponse(oAuth2User.getAttributes());
    }
  }
}

아까 만들었던 CustomOauth2UserService에 적용시켜준다


SecurityConfig에 적용시키기

package com.example.oauth.global.config.security;

import com.example.oauth.domain.auth.service.CustomOauth2UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
  private final CustomOauth2UserService customOauth2UserService;

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(
        auth -> auth
          .requestMatchers("/", "/oauth2/**", "/login/**").permitAll()
          .anyRequest().authenticated()
      )
      .csrf(csrf -> csrf.disable())
      .formLogin(login -> login.disable())
      .httpBasic(basic -> basic.disable())
      .oauth2Login(
        oauth2 -> oauth2
          .userInfoEndpoint(
            userInfoEndpointConfig ->
              userInfoEndpointConfig.userService(customOauth2UserService)
          )
      );

    return http.build();
  }
}

oauth2Login 체인메서드에 다음과 같이 지정해준다

profile
Backend Developer

0개의 댓글