작성
·
598
0
아무리 해도 해결이 안되네요...
package com.example.project1.config.oauth2;
import com.example.project1.config.auth.PrincipalDetails;
import com.example.project1.config.oauth2.provider.GoogleUserInfo;
import com.example.project1.config.oauth2.provider.NaverUserInfo;
import com.example.project1.config.oauth2.provider.OAuth2UserInfo;
import com.example.project1.domain.member.UserType;
import com.example.project1.entity.member.MemberEntity;
import com.example.project1.repository.member.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@Slf4j
@RequiredArgsConstructor
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final MemberRepository memberRepository;
// 구글로부터 받은 userReuest 데이터에 대한 후처리되는 함수
@Override
public PrincipalDetails loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// registrationId로 어떤 OAuth로 로그인 했는지 확인가능
log.info("clientRegistration in PrincipalOauth2UserService : " + userRequest.getClientRegistration() );
log.info("accessToken in PrincipalOauth2UserService : " + userRequest.getAccessToken().getTokenValue() );
OAuth2User oAuth2User = super.loadUser(userRequest);
// 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청
// userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다.
log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User.getAttributes());
// 회원가입을 강제로 진행
OAuth2UserInfo oAuth2UserInfo = null;
if(userRequest.getClientRegistration().getRegistrationId().equals("google")) {
log.info("구글 로그인 요청");
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
} else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
log.info("네이버 로그인 요청");
// 네이버는 response를 json으로 리턴을 해주는데 아래의 코드가 받아오는 코드다.
// response={id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영}
// 위의 정보를 NaverUserInfo에 넘기면
oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"));
} else {
log.info("구글과 네이버만 지원합니다.");
}
// 사용자가 로그인한 소셜 서비스(provider)를 가져옵니다.
// 예를 들어, "google" 또는 "naver"와 같은 값을 가질 수 있습니다.
String provider = oAuth2UserInfo.getProvider();
// 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다.
// 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다.
String providerId = oAuth2UserInfo.getProviderId();
// 예) google_109742856182916427686
String userName = provider + "_" + providerId;
String password = bCryptPasswordEncoder.encode("get");
// 사용자의 이메일 주소를 가져옵니다. 소셜 서비스에서 제공하는 이메일 정보를 사용합니다.
String email = oAuth2UserInfo.getEmail();
// 사용자의 권한 정보를 설정합니다. UserType.
// 여기서는 소셜로그인으로 가입하면 무조건 User로 권한을 주는 방식으로 했습니다.
UserType role = UserType.USER;
// 이메일 주소를 사용하여 이미 해당 이메일로 가입된 사용자가 있는지 데이터베이스에서 조회합니다.
MemberEntity member = memberRepository.findByUserEmail(email);
if(member == null) {
log.info("OAuth 로그인이 최초입니다.");
log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
log.info("OAuth 자동 회원가입을 진행합니다.");
member = MemberEntity.builder()
.userName(userName)
.userPw(password)
.userEmail(email)
.userType(role)
.provider(provider)
.providerId(providerId)
.build();
log.info("userEmail in PrincipalOauth2UserService : " + member.getUserEmail());
log.info("userName in PrincipalOauth2UserService : " + member.getUserName());
log.info("userPw in PrincipalOauth2UserService : " + member.getUserPw());
log.info("userType in PrincipalOauth2UserService : " + member.getUserType());
log.info("provider in PrincipalOauth2UserService : " + member.getProvider());
log.info("providerId in PrincipalOauth2UserService : " + member.getProviderId());
memberRepository.save(member);
} else {
log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다.");
MemberEntity findUser = memberRepository.findByUserEmail(email);
log.info("findUser in PrincipalOauth2UserService : " + findUser);
}
OAuth2User oAuth2User1 = super.loadUser(userRequest);
log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User1.getAttributes());
// attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성
// 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다.
PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes());
log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails);
return principalDetails;
}
}
package com.example.project1.config.oauth2.provider;
public interface OAuth2UserInfo {
String getProviderId();
String getProvider();
String getEmail();
String getName();
}
package com.example.project1.config.oauth2.provider;
import java.util.Map;
public class GoogleUserInfo implements OAuth2UserInfo{
// getAttributes()를 받음
private Map<String, Object> attributes;
public GoogleUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProviderId() {
return (String) attributes.get("sub");
}
@Override
public String getProvider() {
return "google";
}
@Override
public String getEmail() {
return (String) attributes.get("email");
}
@Override
public String getName() {
return (String) attributes.get("name");
}
}
package com.example.project1.config.oauth2.provider;
import java.util.Map;
public class NaverUserInfo implements OAuth2UserInfo{
// oauth2User.getAttributes()를 받음
private Map<String,Object> attributes;
// PrincipalOauth2UserService에서 new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"))로
// Oauth2 네이버 로그인 정보를 받아온다.
// → {id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영}
public NaverUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProviderId() {
return (String)attributes.get("id");
}
@Override
public String getProvider() {
return "naver";
}
@Override
public String getEmail() {
return (String)attributes.get("email");
}
@Override
public String getName() {
return (String)attributes.get("name");
}
}
package com.example.project1.config.auth;
import com.example.project1.entity.member.MemberEntity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
@Setter
@Getter
@ToString
@NoArgsConstructor
@Slf4j
public class PrincipalDetails implements UserDetails, OAuth2User {
// 일반 로그인 정보를 저장하기 위한 필드
private MemberEntity member;
// OAuth2 로그인 정보를 저장하기 위한 필드
// attributes는 Spring Security에서 OAuth2 인증을 수행한 후에 사용자에 대한 추가 정보를 저장하는 데 사용되는 맵(Map)입니다.
// OAuth2 인증은 사용자 인증 후에 액세스 토큰(Access Token)을 발급받게 되는데,
// 이 토큰을 사용하여 OAuth2 서비스(provider)로부터 사용자의 프로필 정보를 요청할 수 있습니다.
// 예를 들어, 소셜 로그인을 사용한 경우에는 attributes에는 사용자의 소셜 서비스(provider)에서 제공하는 프로필 정보가 담겨 있습니다.
// 소셜 로그인 서비스(provider)마다 제공하는 프로필 정보가 다를 수 있습니다.
// 일반적으로 attributes에는 사용자의 아이디(ID), 이름, 이메일 주소, 프로필 사진 URL 등의 정보가 포함됩니다.
/*
* 구글의 경우
* {
"sub": "100882758450498962866", // 구글에서 발급하는 고유 사용자 ID
"name": "John Doe", // 사용자 이름
"given_name": "John", // 이름(이름 부분)
"family_name": "Doe", // 성(성(성) 부분)
"picture": "https://lh3.googleusercontent.com/a/AAcHTtdzQomNwZCruCcM0Eurcf8hAgBHcgwvbXEBQdw3olPkSg=s96-c", // 프로필 사진 URL
"email": "johndoe@example.com", // 이메일 주소
"email_verified": true, // 이메일 주소 인증 여부
"locale": "en" // 지역 설정
}
* */
private Map<String, Object> attributes;
// 일반 로그인
public PrincipalDetails(MemberEntity member) {
this.member = member;
}
// OAuth2 로그인
public PrincipalDetails(MemberEntity member, Map<String, Object> attributes) {
this.member = member;
this.attributes = attributes;
}
// 해당 유저의 권한을 리턴하는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new SimpleGrantedAuthority("ROLE_" + member.getUserType().toString()));
log.info("collection : " + collection);
return collection;
}
// 사용자 패스워드를 반환
@Override
public String getPassword() {
log.info("password : "+ member.getUserPw());
return member.getUserPw();
}
// 사용자 이름 반환
@Override
public String getUsername() {
log.info("id : " + member.getUserEmail());
return member.getUserEmail();
}
// 계정 만료 여부 반환
@Override
public boolean isAccountNonExpired() {
// 만료되었는지 확인하는 로직
// true = 만료되지 않음
return true;
}
// 계정 잠금 여부 반환
@Override
public boolean isAccountNonLocked() {
// true = 잠금되지 않음
return true;
}
// 패스워드의 만료 여부 반환
@Override
public boolean isCredentialsNonExpired() {
// 패스워드가 만료되었는지 확인하는 로직
// true = 만료되지 않음
return true;
}
// 계정 사용 가능 여부 반환
@Override
public boolean isEnabled() {
// 계정이 사용 가능한지 확인하는 로직
// true = 사용 가능
return true;
}
@Override
public Map<String, Object> getAttributes() {
log.info("attributes : " + attributes);
return attributes;
}
@Override
// OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환
public String getName() {
return null;
}
}
// Oauth2 google로 JWT 발급
@GetMapping("/success-oauth")
public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User
, @RequestBody MemberDTO member) {
Object email = oAuth2User.getAttribute("email");
log.info("oAuth2User : " + email);
if(oAuth2User == null) {
log.info("받아올 정보가 없습니다 ㅠㅠ");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어....");
} else {
// OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다.
ResponseEntity<TokenDTO> token = memberService.createToken((String) email, member);
log.info("token : " + token);
return ResponseEntity.ok().body(token);
}
}
// 소셜 로그인 성공시 jwt 반환
// OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드
public ResponseEntity<TokenDTO> createToken(String userEmail, MemberDTO member) {
MemberEntity findUser = memberRepository.findByUserEmail(userEmail);
log.info("findUser in MemberService : " + findUser);
findUser = MemberEntity.builder()
// id를 식별해서 수정
// 이거 없으면 새로 저장하기 됨
// findUser꺼를 쓰면 db에 입력된거를 사용하기 때문에
// 클라이언트에서 userEmail을 전달하더라도 서버에서 기존 값으로 업데이트가 이루어질 것입니다.
// 이렇게 하면 userEmail을 수정하지 못하게 할 수 있습니다.
.userId(findUser.getUserId())
.userEmail(findUser.getUserEmail())
.userPw(passwordEncoder.encode(findUser.getUserPw()))
.userName(findUser.getUserName())
.nickName(member.getNickName())
.userType(findUser.getUserType())
.address(AddressEntity.builder()
.userAddr(member.getAddressDTO().getUserAddr())
.userAddrDetail(member.getAddressDTO().getUserAddrDetail())
.userAddrEtc(member.getAddressDTO().getUserAddrEtc())
.build())
.build();
memberRepository.save(findUser);
// 권한 정보 추출
List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser);
// UserDetails 객체 생성 (사용자의 아이디 정보를 활용)
// 첫 번째 인자 : username 사용자 아이디
// 두 번째 인자 : 사용자의 비밀번호
// 세 번째 인자 : 사용자의 권한 정보를 담은 컬렉션
UserDetails userDetails = new User(userEmail, null, authorities);
log.info("userDetails in MemberService : " + userDetails);
TokenDTO token = jwtProvider.createToken2(userDetails);
log.info("token in MemberService : " + token);
return ResponseEntity.ok().body(token);
}
// 소셜 로그인 성공시 JWT 발급
public TokenDTO createToken2(UserDetails userDetails) {
long now = (new Date()).getTime();
Date now2 = new Date();
// userDetails.getAuthorities()는 사용자의 권한(authorities) 정보를 가져오는 메서드입니다.
// claims.put("roles", userDetails.getAuthorities()) 코드는 사용자의 권한 정보를 클레임에 추가하는 것입니다.
// 클레임에는 "roles"라는 키로 사용자의 권한 정보가 저장되며, 해당 권한 정보는 JWT의 페이로드 부분에 포함됩니다.
Claims claims = Jwts.claims().setSubject(userDetails.getUsername());
claims.put(AUTHORITIES_KEY, userDetails.getAuthorities());
log.info("claims : " + claims);
// access token
Date accessTokenExpire = new Date(now + this.accessTokenTime);
String accessToken = Jwts.builder()
.setSubject(userDetails.getUsername())
.setClaims(claims)
.setIssuedAt(now2)
.setExpiration(accessTokenExpire)
.signWith(key,SignatureAlgorithm.HS256)
.compact();
// RefreshToken 생성
Date refreshTokenExpire = new Date(now + this.refreshTokenTime);
String refreshToken = Jwts.builder()
.setIssuedAt(now2)
.setClaims(claims)
.setExpiration(refreshTokenExpire)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
TokenDTO tokenDTO = TokenDTO.builder()
.grantType("Bearer ")
.accessToken(accessToken)
.refreshToken(refreshToken)
.userEmail(userDetails.getUsername())
.build();
log.info("tokenDTO in JwtProvider : " + tokenDTO);
return tokenDTO;
}
http
// oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다.
.oauth2Login()
// OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당
.userInfoEndpoint()
// OAuth2 로그인 성공 시, 후작업을 진행할 서비스
.userService(principalOauth2UserService)
.and()
.defaultSuccessUrl("/success-oauth");
소셜 로그인 후 쇼핑몰 프로젝트라 주소가 필 수라서 JSON을 추가로 받아서 주소를 DB에 저장 후 JWT를 반환해주는 로직을 구성하려고 합니다. 소셜 로그인을 했을 때 PrincipalOauth2UserService여기서 모든 값이 제대로 들어갔고
PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes());
log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails);
return principalDetails;
로그를 찍어본 결과 제대로 값이 로그에 찍혔습니다. 이거를 principalDetails에 보내줬으니 principalDetails 클래스에서도 제대로 받아졌는지 확인해봤습니다. Oaut2User를 상속받아서 오버라이드 한
@Override
public Map<String, Object> getAttributes() {
log.info("attributes : " + attributes);
return attributes;
}
여기서도 로그에 제대로 값이 나왔습니다.
여기서부터가 문제인데 컨트롤러에서 @AuthenticationPrincipal OAuth2User oAuth2User로 소셜 로그인한 정보를 받아와서 토큰을 만들려고 하는데 null이 뜹니다. 별방법을 다했는데 다른 부분은 다 해결을 했는데 이부분이 해결이 안되네요 ㅠㅠ
답변 1
0
제 강의를 따라서 하셨을때는 잘 되셨나요?
지금 코드가 제 강의가 아닌것 같아요!! 그럼 제가 분석을 해야되서요!!
제 강의를 따라서 하셨던 부분은 잘되었는데..본인 프로젝트로 옮길때 잘 안된다는 말씀이신가요?