[SpringBoot] JWT + Oauth2 추가하기

2025. 4. 7. 22:29· BackEnd/SpringBoot
목차
  1. 1.의존성추가
  2. 2. 구글 OAuth등록하기
  3. 2-1.프로젝트 생성
  4. 2-2. OAuth 클라이언트 ID만들기
  5. 3.application.properties?
  6. 3-1.application-{profile}.properties
  7. 3-2.application-oauth.properties
  8. 3-3. 적용
  9. 4.CustomOAuth2UserService
  10. 5.CustomOAuth2User
  11. 6.OAuth2LoginSuccessHandler
  12. 7.SecurityConfig
  13. 8. 프론트엔드
728x90


1.의존성추가

OAuth 2.0 및 OpenID Connect 기반의 로그인 기능을 쉽게 구현할수 있게해주는 의존성을 추가해준다.
<!-- OAuth2 Client -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

 

 

2. 구글 OAuth등록하기

2-1.프로젝트 생성

우선 아래 링크로 들어가서 새프로젝트를 생성하자

https://console.cloud.google.com/

 

 

 

 

2-2. OAuth 클라이언트 ID만들기

 API 및 서비스 -> 사용자 인증정보로 들어간다.

 

사용자 인증 정보 만들기 -> OAuth 클라이언트 ID를 선택한다.

 

 

 

우선 동의화면을 구성하라고 하니 들어가서 하라는것들을 완료한다.

 

 

 

 

 

 

이제 OAuth 클라이언트 만들기를 선택한다.

 

 

 

어플리케이션 유형은 웹 애플리케이션을 선택하고 이름을 적어준다.

 

 

 

승인된 리디렉션 URI는 다음과같이 작성해야하는데
https://localhost:8080/login/oauth2/code/google

이는 spring-boot-starter-oauth2-client 의존성을 포함하면 Spring Security가 자동으로 만들어주는 URI다.

 

프론트엔드 리다이렉트도 등록하자

 

3.application.properties?

이제 application.properties에 oauth 관련 설정을 추가해야하는데 이는 공개되면 안되는 민감한 정보이기 때문에 따로 관리할것이다.

 

 

3-1.application-{profile}.properties

Spring Boot에서 프로파일 분리용 설정 파일로 다음과 같은 형태로 작성할수있는데 OAuth 관련 민감 정보만 따로 분리해서 보안 관리를 위해 oauth용 파일을 생성할것이다.

 

 

 

3-2.application-oauth.properties

OAuth 관련 민감 정보만 따로 분리한 파일이다.
이 정보들은 매우 민감해서 유출되면 안된다
# google oauth2
spring.security.oauth2.client.registration.google.client-id=YOUR_GOOGLE_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=YOUR_GOOGLE_CLIENT_SECRET   
spring.security.oauth2.client.registration.google.scope=profile,email

 

.gitignore에 이파일을 못올라가게 바로 등록해두자

 

3-3. 적용

application.properties나 application.yml 안에서 다음설정을 하면 application-oauth.properties 파일도 자동으로 로딩되게 설정할 수 있다.
# google oauth2
spring.profiles.include=oauth

 

 

 

 

 

 

4.CustomOAuth2UserService

소셜 로그인(OAuth2)을 처리할 때 사용자 정보를 가져오고, DB에 회원이 존재하는지 확인한 후 필요한 경우 회원가입까지 자동으로 처리하는 클래스다. 소셜 로그인을 쓸 때, 로그인한 사용자 정보를 받아서 우리 서비스의 회원과 연결하는 역할이다.
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final MemberRepository memberRepository;
    
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        // 기본 OAuth2UserService를 이용하여 사용자 정보 가져오기
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        
        // provider 정보 추출 (ex: google)
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        
        // OAuth2 공급자마다 사용자 정보의 key가 다를 수 있음.
        // Google의 경우 "email"로 제공됨.
        Map<String, Object> attributes = oAuth2User.getAttributes();
        String email = (String) attributes.get("email");
        
        // 이메일을 기준으로 기존 회원 검색
        Member member = memberRepository.findBymemberId(email)
            .orElseGet(() -> {
                // 신규 사용자라면 회원가입 처리
                Member newMember = new Member();
                newMember.setMemberId(email);
                newMember.setNickname((String) attributes.get("name")); // 제공되는 이름 정보
                newMember.setProvider(registrationId);
                // OAuth2 인증 사용자는 비밀번호를 사용하지 않으므로 임의의 비밀번호 값 설정 (예: UUID)
                newMember.setPassword(UUID.randomUUID().toString());
                // 기본 Role 설정 (예: CUSTOMER)
                newMember.setRole(Member.Role.CUSTOMER);
                return memberRepository.save(newMember);
            });
        
        // 필요한 경우 기존 회원의 정보 업데이트 가능
        // 예를 들어, 이름이 바뀌었다면 업데이트하는 로직 추가 가능
        
        // 커스텀 OAuth2User 객체로 변환하여 반환
        return new CustomOAuth2User(member, attributes);
    }
}

 

 

 

 

5.CustomOAuth2User

소셜 로그인으로 인증된 사용자의 정보를 Spring Security가 이해할 수 있게 감싸주는 역할을 하는 클래스
Spring Security가 내부적으로 사용하는 OAuth2User 인터페이스를 구현해서, 내 서비스에 맞는 사용자 정보를 담고, 권한 정보도 넘겨주는 역할을 한다.
(Spring Security가 알아들을 수 있도록 래핑해주는 어댑터)
// CustomOAuth2User.java
public class CustomOAuth2User implements OAuth2User {

    private Member member;
    private Map<String, Object> attributes;
    
    public CustomOAuth2User(Member member, Map<String, Object> attributes) {
        this.member = member;
        this.attributes = attributes;
    }
    
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // Member 엔티티의 role 값을 활용하여 권한 부여 (예: ROLE_CUSTOMER, ROLE_SELLER, ROLE_ADMIN)
        return AuthorityUtils.createAuthorityList("ROLE_" + member.getRole().name());
    }
    
    @Override
    public String getName() {
        // Spring Security 내부에서 사용하는 이름 정보 (예시로 nickname 사용)
        return member.getNickname();
    }
    
    // 추가로 필요한 사용자 정보를 메소드로 제공
    public String getEmail() {
        return member.getMemberId();
    }
    
    public Member getMember() {
        return this.member;
    }
}

 

 

 

 

6.OAuth2LoginSuccessHandler

소셜 로그인(OAuth2) 성공 직후 실행되는 로직을 정의하는 클래스
OAuth2 로그인 성공 시 JWT 토큰을 생성하고, 프론트엔드로 리다이렉트하는 역할을 수행한다.

OAuth2 로그인은 구글 서버 → 백엔드까지만 토큰이 전달되므로 프론트로 JWT를 넘기기 위해 직접 리다이렉트를 처리하는 핸들러를 추가한것이다.
@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final JwtTokenProvider jwtTokenProvider; // JWT 유틸 클래스 (직접 만들어야 함)

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
                                        HttpServletResponse response,
                                        Authentication authentication) 
                                        throws IOException, ServletException {
        CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();

        String email = oauthUser.getEmail();
        String role = oauthUser.getAuthorities().iterator().next().getAuthority(); // ex. ROLE_CUSTOMER
        Long id = oauthUser.getMember().getId();
        // JWT 토큰 생성
        String token = jwtTokenProvider.createToken(email, role, id);

        // 응답 헤더에 토큰 추가
        response.setHeader("Authorization", "Bearer " + token);

        // JSON 응답 본문 구성 (프론트에서 토큰 받아서 저장할 수 있게)
        String redirectUrl = "http://localhost:5173/oauth2/redirect?token=" + token;

        response.sendRedirect(redirectUrl);
    }
}

 

 

7.SecurityConfig

이제 SecurityConfig에 oauth2 로그인 엔드포인트을 허용하고 OAuth2 로그인을 내 서비스에 맞게 커스터마이징해 동작하는 설정을 포함한다.
 @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(
                                "/actuator/**", //이건 임시임
                                "/ws/**", // 웹소켓 엔드포인트 임시
                                "/login**", // oauth2 로그인 엔드포인트
                                "/oauth2/**", // oauth2 로그인 엔드포인트
                                "/error", // 에러 페이지
                                "/auth/**",
                                "/member/register",
                                "/h2-console/**",
                                "/product/**",
                                "/seller/**",
                                "/review/**",
                                "/question/**",
                                "/answer/**",
                                "/images/**",
                                "/test/**",
                                "/swagger-ui/**",
                                "/v3/api-docs/**",
                                "/swagger-ui.html",
                                "/test/public")
                        .permitAll()

                        .requestMatchers("/product/create", "/product/update", "/product/delete", "/test/seller")
                        .hasAnyRole("SELLER", "ADMIN")

                        .requestMatchers("/test/customer")
                        .hasAnyRole("CUSTOMER", "ADMIN")

                        .requestMatchers("/test/admin") 
                        .hasRole("ADMIN")
                        
                        .anyRequest().authenticated())
                // CSRF 비활성화 및 세션 정책 설정 (JWT 사용 시)
                .csrf(csrf -> csrf.disable())
                .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                
                // JWT 인증 필터 등록
                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                
                // OAuth2 로그인 설정
                .oauth2Login(oauth2 -> oauth2
                    .loginPage("/login")
                    .userInfoEndpoint(userInfo -> userInfo
                            .userService(customOAuth2UserService))
                    .successHandler(oAuth2LoginSuccessHandler)
                    .failureUrl("/login?error=true"));
		return http.build();
    }

 

 

 

8. 프론트엔드

token을 받을 리다이렉션 부분 추가
<Route path="/oauth2/redirect" element={<OAuth2Redirect />} />

 

oauth 로그인을 위한 페이지 이동
<div className="flex justify-center mt-4">
  <a
    href="http://localhost:8080/oauth2/authorization/google"
    className="px-6 py-2 bg-red-500 text-white font-bold rounded hover:bg-red-600"
  >
    Google 로그인
  </a>
</div>

 

리다이렉션후 uri의 쿼리파라미터에서 토큰 추출후 로컬스토리에 저장한뒤 홈으로 이동
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

function OAuth2Redirect() {
  const navigate = useNavigate();

  useEffect(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const token = urlParams.get("token");

    if (token) {
      localStorage.setItem("token", token);
      navigate("/"); // 홈으로 이동 or 로그인 후 페이지
    } else {
      alert("로그인에 실패했습니다.");
      navigate("/login");
    }
  }, []);

  return <div>로그인 처리 중입니다...</div>;
}

export default OAuth2Redirect;

 

728x90

'BackEnd > SpringBoot' 카테고리의 다른 글

[SpringBoot] WebFlux기반으로 외부 API 받기  (0) 2025.04.28
[SpringBoot] WebSocket 사용하기  (0) 2025.04.05
[SpringBoot] Actuator + Prometheus + Grafana 로 모니터링  (0) 2025.03.24
  1. 1.의존성추가
  2. 2. 구글 OAuth등록하기
  3. 2-1.프로젝트 생성
  4. 2-2. OAuth 클라이언트 ID만들기
  5. 3.application.properties?
  6. 3-1.application-{profile}.properties
  7. 3-2.application-oauth.properties
  8. 3-3. 적용
  9. 4.CustomOAuth2UserService
  10. 5.CustomOAuth2User
  11. 6.OAuth2LoginSuccessHandler
  12. 7.SecurityConfig
  13. 8. 프론트엔드
'BackEnd/SpringBoot' 카테고리의 다른 글
  • [Springboot] vscode Lombok 에러 이슈 Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassDefFoundError: Could not initialize class lombok.javac.Javac
  • [SpringBoot] WebFlux기반으로 외부 API 받기
  • [SpringBoot] WebSocket 사용하기
  • [SpringBoot] Actuator + Prometheus + Grafana 로 모니터링
아사_
아사_
프로그래밍 공부한거 정리해두는 메모장 블로그
아사_
개발공부 블로그
아사_
전체
오늘
어제
  • 분류 전체보기 N
    • FrontEnd
      • html
      • css
      • JavaScript
      • Node.js
      • React
      • React Native
    • BackEnd
      • SpringBoot
      • FastAPI
      • PHP
      • Flask
      • supabase
    • Language
      • Python
      • JAVA
      • Kotlin
      • C++
    • Development Tools N
      • AWS N
      • GIT,GITHUB
      • Docker
      • 메시지 브로커
      • 기타 도구,플랫폼
    • Computer Science
      • 개발지식
      • Server&Network
      • Algorithm&DataStructure
      • Security
      • DataBase
      • OS
    • AI
    • 기타
      • 잡다
      • Android
      • 도서
    • 클론코딩
      • 생활코딩 Express.js
      • 점프 투 장고
      • 생활코딩 Node.js
    • 프로젝트
      • DevQuest

인기 글

최근 글

250x250
hELLO · Designed By 정상우.v4.2.2
아사_
[SpringBoot] JWT + Oauth2 추가하기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.