멋쟁이 사자처럼 13기 강의를 진행하며 만든 자료를 블로그에 공유합니다.
OAuth는 인증(Authentication)과 권한(Authorization) 부여를 위한 개방형 표준 프로토콜입니다!
사용자가 자신의 개인 정보를 직접 제공하지 않고, 다양한 외부 플랫폼(예: 구글, 깃허브, 네이버 등)의 서비스에 안전하게 접근할 수 있도록 돕습니다!
클라이언트(Client) : 리소스에 접근하는 애플리케이션 혹은 서비스 (Spring boot 서버)
인증 서버(Authorization Server) : 인증, 인가를 수행하는 서버로 access token을 발급
리소스 서버(Resource Server) : 구글, 깃허브, 카카오, 네이버 등 사용자의 리소스를 가지고 있는 서버를 의미
인가 코드(Authorization Code) : 사용자가 로그인 성공 후 발급받는 코드로 access token 발급 시 필요

?code=abcd1234 처럼 붙어서 서버로 전달됨"email": "[abc@gmail.com](mailto:abc@gmail.com)", "name": "홍길동" 같은 정보를 JSON으로 넘겨줌
<구글이 해당 서버가 신뢰할 수 있는 클라이언트인지 사전에 식별하고 인증하기 위해 서비스를 등록합니다!>

















애플리케이션 유형 → 웹 애플리케이션
이름 → LikeLion13
승인된 리디렉션 URI → http://localhost:8080/login/oauth2/code/google 을 정확히 입력해 주세요~ (메모장에 입력해 놓아주세요~)
다음 만들기 버튼까지 눌러주시면 됩니다~



https://accounts.google.com/o/oauth2/v2/auth?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&response_type=code&scope=email%20profile
위 링크에 메모장에 적어 놓은 클라이언트 ID와 리디렉션 URI로 변경하여 접속해 볼게요~
아래 사진과 같은 화면이 나온다면 성공입니다~

우선 인가코드를 발급해보는 테스트를 진행해보겠습니다!
이는 위에서 구글 로그인 창으로 이동해, 로그인이 완료 되었다면 리디렉션 URI에 담겨 돌아오는 authorization_code 값을 눈으로 확인해보는 과정입니다!
토큰 강의에서 사용했던 프로젝트를 열어주세요~
그 다음 build.gradle에서 아래 의존성을 추가해주세요~
dependencies {
...
implementation 'com.google.code.gson:gson:2.10.1'
...
}
추가 후 코끼리 눌러주세요~

spring:
profiles:
active: prod
datasource:
url: ${spring.datasource.url}
username: ${spring.datasource.username}
password: ${spring.datasource.password}
driver-class-name: ${spring.datasource.driver-class-name}
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: false
use_sql_comments: true
show_sql: true
open-in-view: false
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type.descriptor.sql: trace
jwt:
secret: ${JWT_SECRET}
access-token-validity-in-milliseconds: ${JWT_ACCESS_TOKEN_VALIDITY_IN_MILLISECONDS}
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
데이터베이스 스키마 생성해 주세요~

spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://localhost:3306/likelionoauth2
username: root
password: #각자 비밀번호로 변경
driver-class-name: com.mysql.cj.jdbc.Driver
jwt:
secret: ??????? # openssl rand -base64 64
access-token-validity-in-milliseconds: 3600000
client-id: 클라이언트 ID
client-secret: 클라이언트 SECRET KEY
token:
expire:
time: 1800000
src/**/application-prod.yml

아래 코드는 OAuth2로그인 흐름을 테스트하기 위해 작성한 최소 구성의 컨트롤러입니다!
구글에서 받은 인가코드를 서버가 받아 AuthLoginServer에게 전달합니다!
package com.likelion.likelionjwt.oauth.api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/login/oauth2")
public class AuthLoginController {
private final AuthLoginService authLoginService;
@GetMapping("/code/{registrationID}")
public void googleLogin(@RequestParam String code, @PathVariable String registrationID){
authLoginService.socialLogin(code, registrationID);
}
}
http://localhost:8080/login/oauth2/code/google?code=abc123 라는 URL이 code : abc123, registrationId : google이 됩니다!이 코드도 테스트를 위한 코드로 실제로 토큰을 요청하거나 사용자 정보를 가져오지 않고 단순히 인가 코드와 소셜로그인의 id(즉 google)을 출력하는 구조입니다!
package com.likelion.likelionjwt.oauth.application;
import org.springframework.stereotype.Service;
@Service
public class AuthLoginService {
public void socialLogin(String code, String registrationId){
System.out.println("code = " + code);
System.out.println("registrationId = " + registrationId);
}
}
저번 토큰 강의에서 작성했던 코드에 이어서 작성할게요~
package com.likelion.likelionjwt.global.config;
import com.likelion.likelionjwt.global.jwt.JwtAuthorizationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration // 하나 이상의 @Bean 메서드를 가지고 있어 spring 컨테이너가 해당 메서드들을 관리하고 빈으로 등록
@EnableWebSecurity // spring security 활성화, 웹 보안 설정 제공
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthorizationFilter jwtAuthorizationFilter;
@Bean // 보안 필터 체인을 정의하는 Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable) // 기본 HTTP 인증 비활성화
.formLogin(AbstractHttpConfigurer::disable) // 스프링 기본 폼 로그인 비활성화
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 세션 정책을 stateless로 설정 -> 서버가 세션을 생성하지 않고 토큰 기반 인증을 사용하도록 설정 = jwt 방식
.csrf(AbstractHttpConfigurer::disable) // csrf 비활성화
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/login/oauth2/**").permitAll()
.requestMatchers("/members/**").permitAll() // members로 시작하는 경로는 허용
.requestMatchers("/posts/**").permitAll() // posts도 허용
.anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
)
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)
// 지정된 필터 앞에 커스텀 필터(jwtAuthorizationFilter) 추가
.formLogin(AbstractHttpConfigurer::disable)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() { // 패스워드 암호화
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
💡
https://accounts.google.com/o/oauth2/v2/auth?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&response_type=code&scope=email%20profile
다시 아까 작성했던 링크로 들어가 로그인 해주세요!!


빈 화면과 콘솔에 인가코드, 구글이 출력되면 성공입니다~
이제 구글로 로그인할 때 처음 방문한 사용자는 회원가입 후 로그인(토큰 발급), 이미 회원가입이 되어있다면 바로 로그인을 해주도록 만들어 보겠습니당~~
지금까지 실습해오던 member 도메인에 이어서 작성해줄게요~
package com.likelion.likelionjwt.member.domain;
import com.likelion.likelionjwt.post.domain.Post;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long memberId;
@Column(name = "member_email", nullable = false)
private String email;
private String password;
@Column(name = "member_name", nullable = false)
private String name;
@Column(name = "member_picture_url")
private String pictureUrl;
@Enumerated(EnumType.STRING)
@Column(name = "member_role", nullable = false)
private Role role;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();
@Builder
public Member(String email, String password, String name, String pictureUrl, Role role) {
this.email = email;
this.password = password;
this.name = name;
this.pictureUrl = pictureUrl;
this.role = role;
}
}
구글로부터 받은 access_token값을 받아서 자바 객체로 변환하거나 서버에서 JWT를 만들어 클라이언트에서 반환할 때 사용하는 DTO 입니다~
package com.likelion.likelionjwt.oauth.api.dto;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@AllArgsConstructor
public class Token {
@SerializedName("access_token")
private String accessToken;
}
MemberInfoResDto가 있는데 왜 또 만드는지 의문이 드시는 분들이 계실 수도 있을 것 같아용!
둘의 차이점은 MemberInfoResDto는 로그인 성공 후 클라이언트에 전달할 정보를 담고 있고, UserInfo는 구글 API로부터 응답받은 JSON을 파싱하는 역할을 합니다!!
즉, UserInfo는 구글로부터 받은 사용자 이름, 이메일, 프로필 사진 등을 담습니다!
package com.likelion.likelionjwt.oauth.api.dto;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
@Data
public class UserInfo {
private String id;
private String email;
@SerializedName("verified_email")
private Boolean verifiedEmail;
private String name;
@SerializedName("given_name")
private String givenName;
@SerializedName("family_name")
private String familyName;
@SerializedName("picture")
private String pictureUrl;
private String locale;
}
Google OAuth2 로그인 후 사용자 정보는 아래와 같은 형태의 JSON으로 응답됩니다~
{
"id": "112345678901234567890",
"email": "user@gmail.com",
"verified_email": true,
"name": "홍길동",
"given_name": "길동",
"family_name": "홍",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GgNEXAMPLE",
"locale": "ko"
}
MemberRepository는 토큰 강의에서 만들었기에 그대로 갑니다~
이는 사용자가 이미 가입된 회원인지 확인하거나 새로 가입시키기 위해 사용됩니다!
package com.likelion.likelionjwt.member.domain.repository;
import com.likelion.likelionjwt.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
boolean existsByEmail(String email);
}작성하던 코드에 이어서 작성해 주세요~
이 클래스는 spring boot 애플리케이션에서 폼로그인 없이 JWT만으로 인증 처리가 가능하도록 만들어 줍니다!
package com.likelion.likelionjwt.global.config;
import com.likelion.likelionjwt.global.jwt.JwtAuthorizationFilter;
import com.likelion.likelionjwt.global.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration // 하나 이상의 @Bean 메서드를 가지고 있어 spring 컨테이너가 해당 메서드들을 관리하고 빈으로 등록
@EnableWebSecurity // spring security 활성화, 웹 보안 설정 제공
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthorizationFilter jwtAuthorizationFilter;
private final JwtTokenProvider jwtTokenProvider;
@Bean // 보안 필터 체인을 정의하는 Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable) // 기본 HTTP 인증 비활성화
.formLogin(AbstractHttpConfigurer::disable) // 스프링 기본 폼 로그인 비활성화
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 세션 정책을 stateless로 설정 -> 서버가 세션을 생성하지 않고 토큰 기반 인증을 사용하도록 설정 = jwt 방식
.csrf(AbstractHttpConfigurer::disable) // csrf 비활성화
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/login/oauth2/**").permitAll()
.requestMatchers("/members/**").permitAll() // members로 시작하는 경로는 허용
.requestMatchers("/posts/**").permitAll() // posts도 허용
.anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
)
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)
// 지정된 필터 앞에 커스텀 필터(jwtAuthorizationFilter) 추가
.formLogin(AbstractHttpConfigurer::disable)
.addFilterBefore(new JwtAuthorizationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() { // 패스워드 암호화
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
추가 후
Cannot resolve symbol 'TokenProvider’
Could not autowire. No beans of 'TokenProvider' type found.
Cannot resolve symbol 'JwtFilter’
위와 같은 에러가 뜨는 건 당연하답니다~
이 코드도 작성하던 곳에 이어서 작성해 주세요~
이 클래스는 OAuth2 로그인 후 사용자 정보를 바탕으로 JWT를 생성하고 클라이언트가 보낸 JWT를 검증해 인증 객체를 만들어줍니다!
package com.likelion.likelionjwt.global.jwt;
import com.likelion.likelionjwt.common.error.ErrorCode;
import com.likelion.likelionjwt.common.exception.BusinessException;
import com.likelion.likelionjwt.member.domain.Member;
import com.likelion.likelionjwt.member.domain.repository.MemberRepository;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j // 로그 작성
@Component // spring bean에 등록
public class JwtTokenProvider {
private static final String AUTHORITIES_KEY = "auth"; // 권한 키 상수 추가
private final MemberRepository memberRepository;
@Value("${token.expire.time}") // factory annotation 임포트
private String tokenExpireTime; // 토큰 만료 시간
@Value("${jwt.secret}")
private String secret; // 비밀키
private SecretKey key; // 객체 key
public JwtTokenProvider(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@PostConstruct // Bean이 초기화 된 후에 실행
public void init() { // 필터 객체를 초기화하고 서비스에 추가하기 위한 메소드 init
byte[] keyBytes = Decoders.BASE64.decode(secret); // 시크릿 키 디코딩 후
this.key = Keys.hmacShaKeyFor(keyBytes); // 키 암호화
}
// 토큰 생성
public String generateToken(Member member) {
// 만료 시간 설정 util로 임포트
Date now = new Date();
Date expireDate = new Date(now.getTime() + Long.parseLong(tokenExpireTime));
return Jwts.builder()
.subject(member.getMemberId().toString()) // 토큰 주체를 id로 설정
.claim(AUTHORITIES_KEY, member.getRole().toString()) // 추가된 권한 Claim
.issuedAt(now) // 발행 시간
.expiration(expireDate) // 만료 시간
.signWith(key, Jwts.SIG.HS256) // ㅏ토큰 암호화
.compact(); // 압축, 서명 후 토큰 생성
}
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token); // 토큰 파싱, 검증
return true; // 검증 완료 -> 유효한 토큰
// 검증 실패 시 반환하는 예외에 따라 다르게 실행
} catch (UnsupportedJwtException | MalformedJwtException e) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "JWT 가 유효하지 않습니다.");
} catch (SignatureException e) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "JWT 서명 검증에 실패했습니다.");
} catch (ExpiredJwtException e) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "JWT 가 만료되었습니다.");
} catch (IllegalArgumentException e) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "JWT 가 null 이거나 비어있거나 공백만 있습니다.");
} catch (Exception e) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "JWT 검증에 실패했습니다.");
}
}
// 인증 객체 반환 core 로 임포트
public Authentication getAuthentication(String token) {
Claims claims = parseClaims(token);
String authority = claims.get(AUTHORITIES_KEY, String.class);
if(authority == null) {
throw new BusinessException(ErrorCode.NO_AUTHORIZATION_EXCEPTION, "권한 정보가 없는 토큰입니다.");
}
List<GrantedAuthority> authorities = Arrays.stream(authority.split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// memberId, 공백, authorities 반환
return new UsernamePasswordAuthenticationToken(claims.getSubject(), "", authorities);
}
// Claims 파싱
private Claims parseClaims(String accessToken) {
try{
JwtParser parser = Jwts.parser()
.verifyWith(key)
.build();
return parser.parseSignedClaims(accessToken).getPayload();
}catch (ExpiredJwtException e){
return e.getClaims();
}
}
}
이 부분도 토큰 강의에서 했던 코드 그대로 갑니다!!
이 필터는 사용자의 요청마다 헤더에서 JWT를 추츨해 검증하고 유효한 경우 해당 사용자를 spring security 인증 객체로 등록합니다!
package com.likelion.likelionjwt.global.jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtAuthorizationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
// 모든 요청이 들어올 때 필터가 가로채 인증 로직 실행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws
IOException, ServletException {
String token = resolveToken((HttpServletRequest) request); // 요청에서 토큰 resolve(추출)
if (token != null && jwtTokenProvider.validateToken(token)) { // 토큰이 유효한지 확인
Authentication authentication = jwtTokenProvider.getAuthentication(token); // 유효하다면 토큰으로부터 인증 정보 가져옴
SecurityContextHolder.getContext().setAuthentication(authentication);
// SecurityContext에 인증 정보 저장 -> 이후 요청 처리에서 인증된 사용자 정보에 접근할 수 있음
}
chain.doFilter(request, response); // 필터 체인의 다음 필터로 요청 넘기기
}
// 요청에서 토큰 추출 메소드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization"); // 헤더에서 Authorization 값 꺼내기
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // "Bearer " 부분을 잘라내고 토큰만 리턴
}
return null;
}
}아까 만들었던 googleLogin부분을 주석처리 후 작성해 주세요!!
구글에서 받은 인가 코드를 받아 액세스 토큰 → 사용자 정보 → JWT 토큰 생성의 과정을 수행하고 클라이언트에게 JWT 액세스 토큰을 응답으로 전달합니다
package com.likelion.likelionjwt.oauth.api;
import com.likelion.likelionjwt.oauth.api.dto.Token;
import com.likelion.likelionjwt.oauth.application.AuthLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/login/oauth2")
public class AuthLoginController {
private final AuthLoginService authLoginService;
// // code : 인증 서버에서 받은 authorization code
// // registrationId : 사용자가 로그인한 소셜 로그인의 id
// @GetMapping("/code/{registrationID}")
// public void googleLogin(@RequestParam String code, @PathVariable String registrationID){
// authLoginService.socialLogin(code, registrationID);
// }
@GetMapping("/code/google")
public Token googleCallback(@RequestParam(name = "code") String code){
String googleAccessToken = authLoginService.getGoogleAccessToken(code);
return loginOrSignup(googleAccessToken);
}
public Token loginOrSignup(String googleAccessToken){
return authLoginService.loginOrSignUp(googleAccessToken);
}
}
Cannot resolve method 'getGoogleAccessToken' in 'AuthLoginService’
Cannot resolve method 'loginOrSignUp' in 'AuthLoginService’
와 같은 오류가 뜨는 것은 정상이랍니다~
이것도 기존에 작성한 코드를 주석처리하고 작성해 주세요~
이 클래스는 OAuth 로그인 후 인가 코드를 통해 access token을 발급받고 사용자 정보를 조회해 회원가입 또는 로그인 처리 후 JWT를 생성해 반환하는 비즈니스 로직을 담당합니다!
package com.likelion.likelionjwt.oauth.application;
import com.google.gson.Gson;
import com.likelion.likelionjwt.global.jwt.JwtTokenProvider;
import com.likelion.likelionjwt.member.domain.Member;
import com.likelion.likelionjwt.member.domain.Role;
import com.likelion.likelionjwt.member.domain.repository.MemberRepository;
import com.likelion.likelionjwt.oauth.api.dto.Token;
import com.likelion.likelionjwt.oauth.api.dto.UserInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class AuthLoginService {
// // 컨트롤러에서 받은 authorization code와 소셜 로그인 id를 받아 콘솔에 출력
// public void socialLogin(String code, String registrationId){
// System.out.println("code = " + code);
// System.out.println("registrationId = " + registrationId);
// }
@Value("${client-id}") // import 시 lombok으로 하면 안됨
private String GOOGLE_CLIENT_ID;
@Value("${client-secret}")
private String GOOGLE_CLIENT_SECRET;
// 구글 인증 코드를 엑세스 토큰으로 교환하는 API 주소
private final String GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
// OAuth 인증 후 구글이 리디렉션할 URI
private final String GOOGLE_REDIRECT_URI = "http://localhost:8080/login/oauth2/code/google";
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
public String getGoogleAccessToken(String code) {
RestTemplate restTemplate = new RestTemplate();
Map<String, String> params = Map.of(
"code", code,
"scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
"client_id", GOOGLE_CLIENT_ID,
"client_secret", GOOGLE_CLIENT_SECRET,
"redirect_uri", GOOGLE_REDIRECT_URI,
"grant_type", "authorization_code"
);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(GOOGLE_TOKEN_URL, params, String.class);
if(responseEntity.getStatusCode().is2xxSuccessful()) {
String json = responseEntity.getBody();
Gson gson = new Gson();
return gson.fromJson(json, Token.class)
.getAccessToken();
}
throw new RuntimeException("구글 엑세스 토큰을 가져오는데 실패했습니다.");
}
public Token loginOrSignUp(String googleAccessToken){
UserInfo userInfo = getUserInfo(googleAccessToken);
if(!userInfo.getVerifiedEmail()){
throw new RuntimeException("이메일 인증이 되지 않은 유저입니다.");
}
Member member = memberRepository.findByEmail(userInfo.getEmail()).orElseGet(() ->
memberRepository.save(Member.builder()
.email(userInfo.getEmail())
.name(userInfo.getName())
.pictureUrl(userInfo.getPictureUrl())
.role(Role.ROLE_USER)
.build())
);
String jwt = jwtTokenProvider.generateToken(member);
return new Token(jwt);
}
public UserInfo getUserInfo(String accessToken){
RestTemplate restTemplate = new RestTemplate();
String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + accessToken;
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);
RequestEntity<Void> requestEntity = new RequestEntity<>(headers, HttpMethod.GET, URI.create(url));
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
if(responseEntity.getStatusCode().is2xxSuccessful()) {
String json = responseEntity.getBody();
Gson gson = new Gson();
return gson.fromJson(json, UserInfo.class);
}
throw new RuntimeException("유저 정보를 가져오는데 실패했습니다.");
}
}
이제 발급받은 Authorization code를 통해 Access Token을 발급받아보겠습니당!
💡https://accounts.google.com/o/oauth2/v2/auth?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&response_type=code&scope=email%20profile
다시 아까 작성했던 링크로 들어가 로그인 해주세요!!
그러면 이렇게 Access Token이 뜰겁니당!!

이제 mysql로 들어가서 조회해볼건데용!

위 사진처럼 회원가입이 되면서 이름, 이메일, 프로필 등 잘 뜨면 성공입니당~~