[Spring OAuth2] Security & OAuth2
- ✅ OAuth2 로그인 정리
✅ OAuth2 로그인 정리
OAuth2를 활용하여 네이버, 카카오, 구글 등의 소셜 로그인을 적용하는 방식과 테크 기업들이 선호하는 처리 방법을 정리한다. 특히, API Gateway를 통과하는 구조를 고려하여 최적의 구현 방식을 설명한다.
🔹 1. OAuth2 로그인 흐름
OAuth2 로그인은 권한 코드(Authorization Code) 플로우를 따른다.
📌 OAuth2 로그인 과정
1️⃣ 사용자가 /oauth2/authorization/naver
로 로그인 요청 2️⃣ Spring Security가 네이버 로그인 페이지로 리디렉트 3️⃣ 사용자가 네이버 로그인 완료 → Authorization Code
를 서버로 반환 4️⃣ 서버가 네이버에 Authorization Code
를 보내고, Access Token
을 요청 5️⃣ 네이버에서 Access Token
과 함께 사용자 정보를 반환 6️⃣ 서버에서 사용자 정보를 DB에 저장하거나 JWT 발급 후 응답
🔹 2. Spring Security 설정
📌 application.yml
설정
spring:
security:
oauth2:
client:
registration:
naver:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
redirect-uri: "http://localhost:8081/oauth2/callback/naver"
client-name: Naver
authorization-grant-type: authorization_code
scope:
- name
- gender
- mobile
kakao:
client-id:
client-secret:
redirect-uri:
client-name: Kakao
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
scope:
- profile_nickname
google:
client-id:
client-secret:
scope:
- email
- profile
provider: # kakao, naver만 추가로 작성
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
naver:
authorization_uri: https://nid.naver.com/oauth2.0/authorize
token_uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user_name_attribute: response
📌 SecurityConfig 설정 (SecurityFilterChain
)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/oauth2/**", "/login/**").permitAll()
.anyRequest().authenticated()
)
// .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) 전체 허용
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfoEndpointConfig ->
userInfoEndpointConfig.userService(customOAuth2UserService)
)
.successHandler(oAuth2LoginSuccessHandler)
);
return http.build();
}
🔹 3. OAuth2 사용자 정보 처리 방식
OAuth2 로그인 후 사용자 정보를 처리하는 방식은 두 가지가 있다.
✅ 1. userInfoEndpoint()
방식 (Spring Security 자동 처리)
- Spring Security가
UserInfo API
를 호출하여 사용자 정보를 가져옴. - Provider(네이버, 카카오, 구글)별로 다른 필드명을 표준화할 수 있음.
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId();
String email = oAuth2User.getAttribute("email");
String name = oAuth2User.getAttribute("name");
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
Map.of("provider", provider, "email", email, "name", name),
"email"
);
}
}
✅ 2. successHandler()
방식 (로그인 성공 후 직접 처리)
- 로그인 성공 후 JWT를 발급하고 응답
- API Gateway를 사용하는 경우 유용함
@Component
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final TokenService tokenService;
public OAuth2LoginSuccessHandler(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
String email = oAuth2User.getAttribute("email");
String name = oAuth2User.getAttribute("name");
String provider = oAuth2User.getAttribute("provider");
String accessToken = tokenService.generateToken(email);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{"accessToken": \"" + accessToken + "\", \"email\": \"" + email + "\", \"name\": \"" + name + "\", \"provider\": \"" + provider + "\"}");
}
}
🔹 4. API Gateway를 통과하는 OAuth2 인증 처리
API Gateway를 사용할 경우, 클라이언트는 JWT를 받아서 API 요청 시 사용해야 함.
📌 API Gateway에서 JWT를 검증하는 방식 1️⃣ 사용자가 /oauth2/authorization/naver
로 로그인 요청 2️⃣ OAuth2 로그인 후 successHandler()
에서 JWT 발급 3️⃣ 클라이언트가 JWT를 Authorization
헤더에 포함하여 API 요청 4️⃣ API Gateway는 JWT를 검증하여 유효한 요청만 Backend 서비스로 전달
📌 OAuth2 로그인 후 응답 예제
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI...",
"email": "user@example.com",
"name": "홍길동",
"provider": "naver"
}
📌 클라이언트에서 API 요청 시 JWT 사용
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI...
🔹 5. userInfoEndpoint()
+ successHandler()
조합 방식의 장점
| 기능 | userInfoEndpoint()
| successHandler()
| |——|———————|——————-| | OAuth2 사용자 정보 가져오기 | ✅ 자동 호출 | ❌ 직접 호출 필요 | | OAuth2 Provider 표준화 | ✅ Provider별 필드 통합 가능 | ❌ 직접 매핑 필요 | | JWT 발급 | ❌ 별도 구현 필요 | ✅ JWT 발급 가능 | | API Gateway 지원 | ❌ 기본 지원 X | ✅ JWT 기반 인증 가능 | | 로그인 성공 후 추가 처리 | ❌ 추가 로직 적용 어려움 | ✅ DB 저장, 리디렉션 가능 |
🎯 최종 정리
✔ OAuth2 로그인 후 userInfoEndpoint()
에서 사용자 정보를 가져오고, successHandler()
에서 JWT 발급 및 추가 로직을 처리하는 방식이 일반적 ✔ API Gateway를 사용할 경우 successHandler()
에서 JWT를 발급하고, Backend 서비스는 JWT 검증만 수행 ✔ MSA 환경에서는 userInfoEndpoint()
+ successHandler()
조합을 통해 인증을 최적화할 수 있음
🚀 즉, 테크 기업들은 API Gateway + JWT 조합을 통해 OAuth2 인증을 처리하는 경우가 많고, userInfoEndpoint()
를 활용하여 표준화한 후 successHandler()
에서 추가 처리를 수행하는 방식을 선호함!