목차
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 |
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 |