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'
}
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();
}
}
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
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
에러가 발생한다
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) 처리 로직을 작성한다
종류별로 분리하는 이유는 각각 응답하는 형태가 다르기 때문이다
추후 알아보도록 하자
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를 만들어서 상속받아 쓰겠다)
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()
를 활용하여 값을 가져온다
@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에 적용시켜준다
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 체인메서드에 다음과 같이 지정해준다