[골든리포트!] 3) Spring Security Config로 스프링 OAuth 다루기

2026. 2. 10. 12:12·개인 프로젝트/[2026] 골든리포트!

SecurityConfig 작성하기

로그인 기능을 구현해야한다. 폼로그인은 사용하지 않을 것이고.. 네이버 OAuth 로그인만 사용할 것이다.

 

OAuth 로그인을하려면 가장먼저 Spring Security 설정을 해주어야한다.

package com.example.GoldenReport.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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("/", "/css/**", "/js/**", "/images/**", "/favicon.ico/**").permitAll()
                        .requestMatchers(HttpMethod.GET, "/review/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2 -> oauth2
                        .defaultSuccessUrl("/")
                        .failureUrl("/login?error=true")
                )
                .logout(logout -> logout
                        .logoutSuccessUrl("/")
                );

        return http.build();
    }
}

이 SecurityConfig를 통해서 애플리케이션 전반에 대한 보안처리를 하게된다. oauth2Login이 아니라 formLogin을 적으면 OAuth 로그인이 아니라 폼로그인을 지원하게되는 것이고..

 

하나에 대해서 알아보면

authorizeHttpRequest → 특정 URL에 대해 인증여부를 결정한다. requestMatchers()를 통해서 URL을 지정해줄 수 있다. 매개변수는 Http메서드(없어도됨), URL주소가 있다.

 

그래서 위 코드는 "/", "/css"로 시작하는 하위주소, "js"로 시작하는 하위주소 등등...에 대해 모든 요청을 인증상태가 아니더라도 요청을 허가하고, "/review"로 시작하는 URL에서 GET 요청이 들어올 때 인증상태가 아니더라도 요청을 허가, 이외의 모든 URL은 반드시 인증이 필요하도록 설정한다.

 

oauth2Login 메서드는 defaultSuccessUrl (로그인 성공시 반환 URL), failureUrl (로그인 실패시 반환 URL)을 지정하고

logout 메서드를 통해 로그아웃 성공 시 돌아갈 URL을 지정한다.

 

이렇게 Config 파일을 설정하면, SecurityFilterChain이 생성된다. 이 SecurityFilterChain은 들어오는 모든 요청을 가로채서 이 보안설정들을 수행하게된다. URL주소를 확인하고 인증여부도 확인하고 등등... 이 인증여부 확인은 스프링에서 기본제공하는 세션로그인 방식이다. 따라서 간단한 개발의 경우 OAuth 로그인과 JWT토큰 인증방식을 모두 사용하는 것은 그렇게 좋은 방식이 아니라고.

 

제미나이에게 몇 가지 더 물어보니 타임리프를 활용하거나 전통적인 방식(?)에서는 스프링 세션로그인 방식을 사용하고, 프론트엔드가 분리되거나 쿠키가 없는 모바일 앱 백엔드 등에는 세션 + JWT를 쓴다고한다.


OAuth 기본 속성값 전달하기

https://dev-dx2d2y-log.tistory.com/211

 

[백엔드] OAuth는 어떻게 진행되는가?

내가 처음 백엔드 개발할 때 담당했던 기능이 로그인, 그리고 GDG 프로젝트세션에 들어와서 처음 만진 것도 로그인, 그리고 이 블로그의 첫 글도 로그인.. 그만큼 잘 까먹는 기능이기도하기 때문

dev-dx2d2y-log.tistory.com

암튼 저번에 OAuth에 대해서 알아보았다.

 

spring:
  security:
    oauth2:
      client:
        registration:
          naver:
            client-id: ##
            client-secret: ##
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/naver
            scope: profile, email
        provider:
          naver:
            authorization-url: 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

스프링을 사용하면 저번에 OAuth에 대해 다뤘을 때 클라이언트 ID와 시크릿, 그리고 리다이렉트 URL을 Authorization 서버측으로 알아서 자동으로 보낸다. application.yml 파일에서는 이 필요한 속성들을 명시해주기만하면 된다.

 

가장 기본적인 리다이렉트 주소는 (BaseUrl)/login/oauth2/code/(registrationId). registrationId는 Authorization 서버 이름이다. 네이버면 naver, 구글이면 google

 

아무튼 이렇게 사전설정까지 다 적어주면 우리가 네이버로 OAuth 요청을 보내면 자동으로 스프링이 저 클라이언트 ID, 시크릿, 리다이렉트 URL을 담아서 같이 보낸다.


그럼 네이버 로그인을 촉발시키는 엔드포인트는 어디에 있을까?

결국에는 네이버 서버로 로그인 OAuth 요청을 보내야하기 때문인데.. 이 역시 스프링이 알아서해준다.

 

위의 application.yml 파일을 보면

spring.security...provider.naver에 하위 속성들이 있다. 이 URL들이 OAuth에 스프링이 사용할 URL들이다.

 

authorization-url → 네이버로 OAuth 요청을 보낼 URL. OAuth의 시작이며, 백엔드에서는 별다른 엔드포인트를 만들어둘 필요 없이 프론트엔드가 authorization-url의 엔드포인트를 알고 적절히 이동시켜놓으면된다.

token-url → 리다이렉트 URL을 통해서 임시코드를 알고나서, OAuth의 AccessToken을 발급받기위해 접근하는 URL이다.

user-info-uri → AccessToken을 받고나서 실제 유저정보를 얻을 URL이다.

 

https://dev-dx2d2y-log.tistory.com/5

 

[GDG] 홍대 맛집 아카이빙 프로젝트 백엔드 개발 #1 - OAuth에 대해서...

대학교 졸업할 때는 풀스택 개발자가 되기를 기원하며 백엔드 개발부터 시도했다. (사실 몇 년 전에 프론트엔드 맛보기 작업을 몇 개 했는데 내 스타일이 아니어서...) 백엔드 개발공부의 일환으

dev-dx2d2y-log.tistory.com

내가 예전에 개발한 OAuth 개발에 비추어보면, 그 때는 모든 백엔드에서 직접 authorization-url로 다시 요청을 보냈고했는데 굳이 그럴 필요가 없다는거


SuccessHandler로 후속처리하기

그럼 이제 스프링에게서 유저정보를 받아야한다.

간단한 기능이라면 스프링이 알아서 OAuth 로그인도 처리하고 세션에 이 정보를 넣어두어서 세션로그인 방식을 잘 활용하면된다.

 

하지만 단순히 세션로그인 방식이 아니고, 이 로그인유저의 정보가 있는지 확인해야하거나, JWT토큰을 발급해야하는 등, 몇 가지 부가적인 기능이 있을 수 있다.

 

이럴 때에는 SuccessHandler를 사용할 수 있다.

이전에는 로그인 정보에 추가로 JWT토큰이나 회원가입 여부 등을 검사하려면 로그인 성공 시 /login/success 로 리다이렉트 시킨 다음에 로직을 추가해야했지만, SecurityConfig에 SuccessHandler를 추가하면 자동으로 스프링이 핸들러 코드까지 실행시킨다.

 

게다가 스프링에서는 SimpleUrlAuthenticationSuccessHandler 기능을 제공하기 때문에, 몇 가지 스프링의 혜택도 받을 수 있다.

 

package com.example.GoldenReport.Service.LogInAndSignUp;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import java.util.Map;

public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) {

        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        Map<String, Object> oAuth2UserResponse = oAuth2User.getAttribute("response");
        String userId = oAuth2UserResponse.get("id").toString();

    }
}

네이버 로그인 시에는 응답의 response 속성 내부에 id 속성으로 식별자가 전해지므로 Map을 통해서 식별자를 빼낸다. 이 식별자가 UserId가 될 것이다.

 

package com.example.GoldenReport.Domain;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
    @Id
    private long id;

    private position position;
}

이렇게 Member 테이블에 들어갈 요소들을 지정해주었다.

 

package com.example.GoldenReport.Domain;

import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum position {
    USER("ROLE_USER", "일반 사용자"),
    ADMIN("ROLE_ADMIN", "관리자");

    private final String key;
    private final String name;
}

권한을 나타내는 position 변수에는 이전에 배운 ENUM을 사용해보았다. 개발을하면 할수록 이렇게 값을 미리 지정해두는게 편하고 오히려 권한을 문자열로 지정하는게 불편하고 불안하달까

 

public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    MemberRepository memberRepository;

    public OAuth2SuccessHandler(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) {

        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        Map<String, Object> oAuth2UserResponse = oAuth2User.getAttribute("response");
        String userId = oAuth2UserResponse.get("id").toString();

        boolean isMember = memberRepository.existsById(userId);

        if (isMember) {

        } else {
            
        }
    }
}

스프링에서는 OAuth 과정을 마치고 SimpleUrlAuthenticationSuccessHandler에다가 onAuthenticationSuccess 메서드의 매개변수로 HttpServletRequest, HttpServletResponse, Authenticaion 변수를 전해준다. 각각 순수한 HTTP 요청과 응답, 그리고 OAuth2 로그인 결과에 해당한다. 그래서 OAuth2 로그인 결과는 authentication 변수에서 가져오는 것이고, HTTP 응답을 반환하려면 ResponseEntity를 사용하는게 아니라 HttpServletResponse에 직접 응답을 기재한다.

 

만약 멤버가 DB에 존재하지 않으면 회원가입 창으로 사용자를 리다이렉트 시켜야한다.

package com.example.GoldenReport.Service.LogInAndSignUp;

import com.example.GoldenReport.Domain.Member;
import com.example.GoldenReport.Repository.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;

public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    MemberRepository memberRepository;

    public OAuth2SuccessHandler(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) {

        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        Map<String, Object> oAuth2UserResponse = oAuth2User.getAttribute("response");
        String userId = oAuth2UserResponse.get("id").toString();

        Optional<Member> member = memberRepository.findById(userId);

        if (member.isPresent()) {
            System.out.println("There is Member with userId: " + userId);
        } else {
            try {
                System.out.println("There is No Member with userId: " + userId);

                response.setStatus(HttpServletResponse.SC_FOUND);
                response.sendRedirect("/signup");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

onAuthenticationSuccess 메서드는 상속된 메서드이므로 반드시 반환타입이 void여야한다. 따라서 ResponseEntity는 사용하지 못하는데, 스프링에서 HttpServletResponse를 매개변수로 주었으므로 이를 활용하여 반환값을 정할 수 있다.

 

우선 Member 정보를 사용하기 위해서 member 변수를 findById() 메서드를 사용해서 바꿨고, 복잡한 로직을 넣지는 않고, 우선은 멤버 정보만 출력하는 로직을 짰다.

 

response.setStataus 메서드는 HttpStatus를 지정하고, status가 302가 되었으므로 sendRedirect를 통해서 옮겨갈 URL을 지정한다. 이 과정에서 IOE가 발생할 수 있으므로 예외처리도 진행해야한다.


Config에 SuccessHandler 추가하기

그리고 이 SuccessHandler를 Config에 달아주어 스프링에게 로그인 성공 시 어떤 로직을 수행해야할지 알려주기만하면 된다.

 

package com.example.GoldenReport.Config;

import com.example.GoldenReport.Service.LogInAndSignUp.OAuth2SuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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{
    OAuth2SuccessHandler oauth2SuccessHandler;

    public SecurityConfig(OAuth2SuccessHandler oauth2SuccessHandler){
        this.oauth2SuccessHandler = oauth2SuccessHandler;
    }

    @Bean
    public SecurityFilterChain filterChain (HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/css/**", "/js/**", "images/**", "favicon.ico/**").permitAll()
                        .requestMatchers(HttpMethod.GET, "/review/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2 -> oauth2
                        .defaultSuccessUrl("/")
                        .successHandler((request, response, authentication) -> {
                            oauth2SuccessHandler.onAuthenticationSuccess(request, response, authentication);
                        })
                        .failureUrl("/login?error=true")
                )
                .logout(logout -> logout
                        .logoutSuccessUrl("/")
                );

        return http.build();
    }
}

이렇게 config 클래스의 http.oauth2Login에 successHandler 메서드에 달아주기만하면 된다.

 

package com.example.GoldenReport.Domain;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
    @Id
    private String id;

    private position position;
}

네이버에서 전해주는 식별자를 Member의 id로 쓸거라 이렇게 두고 우선은 테스트

 

이렇게 결과가 잘 도착했다.


이렇게 OAuth2 로그인방식을 처리하고, 스프링이 준 값을 받아서 후속로직을 추가하는 방법에 대해서 알아보았다.

이전에는 Config 클래스에서 로직을 이리저리 추가하거나, 로그인이 끝나면 /login/success URL로 리다이렉트시킨 후에 후속로직을 처리했지만, 사실 SuccessHandler로 이를 처리할 수 있다.

 

이외에도 OAuth2에서 하나 윗단계, 그러니까 Authorization 서버에서 AccessToken을 발급받은 후에 사용하는 클래스도 직접 작성할 수 있다.

 

https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.html

 

DefaultOAuth2UserService (spring-security-docs 7.0.2 API)

Use this strategy to adapt user attributes into a format understood by Spring Security; by default, the original attributes are preserved. This can be helpful, for example, if the user attribute is nested. Since Spring Security needs the username attribute

docs.spring.io

https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserService.html

 

OAuth2UserService (spring-security-docs 7.0.2 API)

All MethodsInstance MethodsAbstract Methods Returns an OAuth2User after obtaining the user attributes of the End-User from the UserInfo Endpoint.

docs.spring.io

DefaultOAuth2UserService의 구현체를 직접 만들 수 있는데, (DefaultOAuth2UserService는 OAuth2UserService의 구현체인데, OAuth2UserService의 모든 기능을 구현하기는 매우 어려우므로 DefaultOAuth2UserService를 오버라이드하는 편)

 

DefaultOAuth2UserService의 구현체를 사용하면, AccessToken을 받아서 요청을 보내는 과정은 직접 만들어야한다. 이 과정까지 스프링에게 맡기면 아까봤던 SuccessHandler 방식을 사용하는 것이고.

 

DefaultOAuth2UserService의 구현체를 사용하면, 여기서 오류가 발생하거나 오류를 발생시키면, FailureHandler에서 잡을 수 있다. 즉, DB에 유저정보가 없거나 하는 등의 오류를 직접 일일이 처리하는게 아니라 FailureHandler에서 이를 잡아서 일괄적으로 처리하거나 여러 에러들에 대해 공통적인 로직을 구현할 수 있다는 뜻이다.


이후 해야할 것

- JWT 토큰 발급하기

- 회원가입 기능개발

- 영화리뷰기능과 연결하기

'개인 프로젝트 > [2026] 골든리포트!' 카테고리의 다른 글

[골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기  (0) 2026.02.13
[골든리포트!] 5) JWT 토큰 후속기능 처리하기  (0) 2026.02.12
[골든리포트!] 4) 스프링 세션 설정 및 JWT토큰 발급  (0) 2026.02.10
[골든리포트!] 2) TMDB API 가져와서 리뷰 작성기능 넣기  (0) 2026.02.05
[골든리포트!] 1. 영영 볼 수 없는 리뷰 작성 설계하기  (0) 2026.01.24
'개인 프로젝트/[2026] 골든리포트!' 카테고리의 다른 글
  • [골든리포트!] 5) JWT 토큰 후속기능 처리하기
  • [골든리포트!] 4) 스프링 세션 설정 및 JWT토큰 발급
  • [골든리포트!] 2) TMDB API 가져와서 리뷰 작성기능 넣기
  • [골든리포트!] 1. 영영 볼 수 없는 리뷰 작성 설계하기
Radiata
Radiata
개발을 합니다.
  • Radiata
    DDD
    Radiata
  • 전체
    오늘
    어제
    • 분류 전체보기 (211) N
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59) N
        • JVM (12)
        • 백엔드 (20) N
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 블로그 메뉴

    • CS
    • 언어공부
    • 개인 프로젝트
    • 팀 프로젝트
    • 알고리즘
    • 고찰
    • 신년사
    • 컬러잇 개발블로그
  • 링크

    • 컬러잇 개발블로그
  • 공지사항

  • 인기 글

  • 태그

    144
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Radiata
[골든리포트!] 3) Spring Security Config로 스프링 OAuth 다루기
상단으로

티스토리툴바