작성
·
837
0
강사님 수업을 보면 정보를 다 가지고 오는 것을 볼 수 있는데
package com.example.oauth.config.OAuth2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.List;
import java.util.Map;
public interface ProviderUser {
String getId();
String getUserName();
String getPassword();
String getEmail();
String getProvider();
List<SimpleGrantedAuthority> getAuthorities();
Map<String, Object> getAttributes();
}
package com.example.oauth.config.OAuth2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
// 여기는 공통적인 부분만 추상화해서 모아놓은 곳이다.
// Google, Naver에서 다른 부분들은 따로 만들어줄 것이다.
public abstract class OAuth2ProviderUser implements ProviderUser{
private Map<String, Object> attributes;
private OAuth2User oAuth2User;
private ClientRegistration clientRegistration;
public OAuth2ProviderUser(Map<String, Object> attributes,
OAuth2User oAuth2User,
ClientRegistration clientRegistration) {
this.attributes = attributes;
this.oAuth2User = oAuth2User;
this.clientRegistration = clientRegistration;
}
@Override
public String getPassword() {
return UUID.randomUUID().toString();
}
@Override
public String getEmail() {
return (String) getAttributes().get("email");
}
@Override
public List<SimpleGrantedAuthority> getAuthorities() {
return oAuth2User.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList());
}
@Override
public String getProvider() {
return clientRegistration.getRegistrationId();
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
}
package com.example.oauth.config.OAuth2;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Map;
public class NaverUser extends OAuth2ProviderUser{
public NaverUser(OAuth2User oAuth2User, ClientRegistration clientRegistration) {
super((
Map<String, Object>) oAuth2User.getAttributes().get("response"),
oAuth2User,
clientRegistration);
}
@Override
public String getId() {
return (String) getAttributes().get("id");
}
@Override
public String getUserName() {
return (String) getAttributes().get("name");
}
}
package com.example.oauth.config.OAuth2;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.OAuth2User;
public class GoogleUser extends OAuth2ProviderUser{
public GoogleUser(OAuth2User oAuth2User, ClientRegistration clientRegistration) {
// 사용자의 정보는 oAuth2User.getAttributes() 여기에 담겨져 있다.
// 여기는 클레임 형식 즉, Map 형식으로 되어 있다.
super(oAuth2User.getAttributes(), oAuth2User, clientRegistration);
}
@Override
// 식별자의 역할
public String getId() {
return (String) getAttributes().get("sub");
}
@Override
// 유저 id
public String getUserName() {
return (String) getAttributes().get("name");
}
}
package com.example.oauth.config.OAuth2;
import com.example.oauth.repository.UserRepository;
import com.example.oauth.service.UserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
// OAuth2UserService : Spring Security에서 OAuth 2.0을 사용하여 인증한 사용자 정보를 가져오기 위한 인터페이스입니다.
// 이 인터페이스를 구현하여 사용자 정보를 가져오는 방법을 정의하고,
// OAuth 2.0 프로바이더(예: Google, Facebook, GitHub 등)로부터 인증된 사용자 정보를 추출할 수 있습니다.
// OAuth2UserRequest : 이 객체는 OAuth 2.0 클라이언트 정보, 권한 부여 코드, 액세스 토큰 등을 포함합니다.
// 이 정보를 사용하여 사용자 정보를 요청하고 처리할 수 있습니다.
// OAuth2User : OAuth 2.0 프로바이더(인증 제공자)로부터 가져온 인증된 사용자 정보를 나타냅니다.
// 이 정보는 사용자의 프로필 데이터, 권한(스코프), 사용자 ID 등을 포함할 수 있습니다.
public class CustomOAuth2UserService extends AbstractOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
public CustomOAuth2UserService(UserRepository userRepository, UserService userService) {
super(userRepository, userService);
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// ClientRegistration은 Spring Security에서 OAuth 2.0 또는 OpenID Connect (OIDC) 클라이언트
// 애플리케이션의 등록 정보를 나타내는 클래스입니다. 클라이언트 애플리케이션의 설정 및 속성을 포함합니다.
// userRequest.getClientRegistration()은 인증 및 인가된 사용자 정보를 가져오는
// Spring Security에서 제공하는 메서드입니다.
ClientRegistration clientRegistration = userRequest.getClientRegistration();
OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService =
new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest);
// 여기에는 구글, 네이버 정보가 담겨져 있다.
ProviderUser providerUser = super.providerUser(clientRegistration, oAuth2User);
// 회원가입
super.register(providerUser, userRequest);
return oAuth2User;
}
}
package com.example.oauth.config.OAuth2;
import com.example.oauth.entity.UserEntity;
import com.example.oauth.repository.UserRepository;
import com.example.oauth.service.UserService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
@Getter
@RequiredArgsConstructor
@Log4j2
// 사용자 등록과 어떤 사용자인지 알아볼 수 있는 곳
public abstract class AbstractOAuth2UserService {
private final UserRepository userRepository;
private final UserService userService;
protected void register(ProviderUser providerUser, OAuth2UserRequest userRequest) {
UserEntity findUser = userRepository.findByUserName(providerUser.getUserName());
if(findUser == null) {
String registrationId = userRequest.getClientRegistration().getRegistrationId();
userService.register(registrationId, providerUser);
} else {
log.info("user : " + findUser);
}
}
protected ProviderUser providerUser(ClientRegistration clientRegistration, OAuth2User oAuth2User) {
String registrationId = clientRegistration.getRegistrationId();
if(registrationId.equals("google")) {
return new GoogleUser(oAuth2User, clientRegistration);
} else if(registrationId.equals("naver")) {
return new NaverUser(oAuth2User, clientRegistration);
} else {
return null;
}
}
}
이거를 REST 방식으로 하려고
컨트롤러
// 소셜 로그인
@GetMapping("/api/v1/user/success")
public ResponseEntity<?> socialLogin(Authentication authentication,
@AuthenticationPrincipal OAuth2User oAuth2User) {
OAuth2AuthenticationToken oAuth2AuthenticationToken = (OAuth2AuthenticationToken) authentication;
if (oAuth2AuthenticationToken != null) {
ResponseEntity<?> oAuth2Login =
memberService.login(oAuth2AuthenticationToken, oAuth2User);
return ResponseEntity.ok().body(oAuth2Login);
} else {
return ResponseEntity.badRequest().build();
}
}
서비스
// 소셜 로그인
public ResponseEntity<?> login(OAuth2AuthenticationToken oAuth2AuthenticationToken,
OAuth2User oAuth2User) {
Map<String, Object> attributes = oAuth2User.getAttributes();
log.info("attributes : " + attributes);
String authorizedClientRegistrationId =
oAuth2AuthenticationToken.getAuthorizedClientRegistrationId();
String email = null;
TokenDTO jwt = null;
if (authorizedClientRegistrationId.equals("google")) {
email = (String) attributes.get("email");
log.info("email : " + email);
jwt = createJWT(email);
log.info("jwt : " + jwt);
} else if (authorizedClientRegistrationId.equals("naver")) {
Map<String, Object> response = (Map) attributes.get("response");
email = (String) response.get("email");
jwt = createJWT(email);
log.info("jwt : " + jwt);
} else {
log.info("아무런 정보를 받지 못했습니다.");
}
return ResponseEntity.ok().body(jwt);
}
private TokenDTO createJWT(String email) {
MemberEntity findUser = memberRepositroy.findByEmail(email);
List<GrantedAuthority> authoritiesForUser = getAuthoritiesForUser(findUser);
TokenDTO tokenForOAuth2 = jwtProvider.createTokenForOAuth2(email, authoritiesForUser);
TokenEntity findToken = tokenRepository.findByMemberEmail(tokenForOAuth2.getMemberEmail());
if (findToken == null) {
TokenEntity tokenEntity = TokenEntity.toTokenEntity(tokenForOAuth2);
tokenRepository.save(tokenEntity);
log.info("token : " + tokenForOAuth2);
} else {
tokenForOAuth2 = TokenDTO.builder()
.id(findToken.getId())
.grantType(tokenForOAuth2.getGrantType())
.accessToken(tokenForOAuth2.getAccessToken())
.refreshToken(tokenForOAuth2.getRefreshToken())
.memberEmail(tokenForOAuth2.getMemberEmail())
.build();
TokenEntity tokenEntity = TokenEntity.toTokenEntity(tokenForOAuth2);
tokenRepository.save(tokenEntity);
}
return tokenForOAuth2;
}
이렇게 구성을 했는데 JWT는 제외하고 public ResponseEntity<?> socialLogin(Authentication authentication,
@AuthenticationPrincipal OAuth2User oAuth2User) { 이거로 정보를 가지고 오려고 했는데 정보를 안가지고 와지고 계속 JWT 검증 로직에 걸려서 잘못된 JWT라고 뜹니다 ㅠㅠ
package com.example.study01.config.jwt;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Log4j2
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
public static final String HEADER_AUTHORIZATION = "Authorization";
private final JwtProvider jwtProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// request header에서 JWT를 추출
// 요청 헤더에서 JWT 토큰을 추출하는 역할
String jwt = resovleToken(httpServletRequest);
log.info("jwt in JwtAuthenticationFilter : " + jwt);
// 어떤 경로로 요청을 했는지 보여줌
String requestURI = httpServletRequest.getRequestURI();
log.info("uri JwtAuthenticationFilter : " + requestURI);
if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) {
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(jwt);
log.info("authentication in JwtAuthenticationFilter : " + authentication);
// Spring Security의 SecurityContextHolder를 사용하여 현재 인증 정보를 설정합니다.
// 이를 통해 현재 사용자가 인증된 상태로 처리됩니다.
// 위에서 jwtProvider.getAuthentication(jwt)가 반환이 UsernamePasswordAuthenticationToken로
// SecurityContext에 저장이 되는데 SecurityContextHolder.getContext().setAuthentication(authentication);
// 처리를 하는 이유는 다음과 같다.
/*
* 1. 인증 정보 검증: JWT 토큰이나 다른 인증 정보를 사용하여 사용자를 식별하고
* 권한을 확인하기 위해서는 토큰을 해독하여 사용자 정보와 권한 정보를 추출해야 합니다.
* 이 역할은 jwtProvider.getAuthentication(jwt)에서 수행됩니다.
* 이 메서드는 JWT 토큰을 분석하여 사용자 정보와 권한 정보를 추출하고, 해당 정보로 인증 객체를 생성합니다.
*
* 2. 인증 정보 저장:
* 검증된 인증 객체를 SecurityContextHolder.getContext().setAuthentication(authentication);를
* 사용하여 SecurityContext에 저장하는 이유는, Spring Security에서 현재 사용자의 인증 정보를
* 전역적으로 사용할 수 있도록 하기 위함입니다. 이렇게 하면 다른 부분에서도 현재 사용자의 인증 정보를 사용할 수 있게 되며,
* Spring Security가 제공하는 @AuthenticationPrincipal 어노테이션을 통해 현재 사용자 정보를 편리하게 가져올 수 있습니다.
* */
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
log.error("유효한 JWT가 없습니다. : " + requestURI);
}
filterChain.doFilter(request, response);
}
// 토큰을 가져오기 위한 메소드
// Authorization로 정의된 헤더 이름을 사용하여 토큰을 찾고
// 토큰이 "Bearer "로 시작하거나 "Bearer "로 안온 것도 토큰 반환
private String resovleToken(HttpServletRequest httpServletRequest) {
String token = httpServletRequest.getHeader(HEADER_AUTHORIZATION);
// 토큰이 포함하거나 Bearer 로 시작하면 true
if(StringUtils.hasText(token) && token.startsWith("Bearer ")) {
return token.substring(7);
} else if(StringUtils.hasText(token)) {
return token;
} else {
return null;
}
}
}
이럴 경우는 어떻게 해야하나요... 컨트롤러에서 정보를 가지고 와서 그 정보로 JWT를 만들어 반환하려고 하는데 정보를 가지고 오는게 계속 실패를 해서...
답변 2
0
소스를 다운받아 실행해 보면 여러 군데서 에러가 납니다.
Could not resolve placeholder 'jwt.secret_key' in value "${jwt.secret_key}"
그리고 위 오류를 해결하더라도
Consider defining a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' in your configuration.
와 같은 에러가 납니다.
일단 소스 실행에 오류가 나지 않도록 재 업로드 부탁드립니다.
0
어라? 저는 그런 오류가 안나는데 저는 단순히 JWT 검증에서 걸리고 말씀하신 오류가 발생하지 않습니다.
일단 소셜 로그인을 할 경우 이런식으로 정보를 가지고 오고
이렇게 DB에도 들어가지만
헤더로 소셜 로그인 토큰을 받으면 이렇게 뜨고 토큰을 헤더에 안받으면 토큰이 없다고 정보를 가지고 올 수 없습니다...
https://github.com/YuYoHan/boot_study/tree/main
다시 올려봅니다. 혹시 oauth.yml 과 jwt.yml 이런 설정 파일이 없어서 그러는 걸까요? 그런거는 git 안올라가게 조심하라고 해서 안올라가게 막아 놨는데