Springboot SpringSecurity+JWT+OAuth2 소셜 로그인 구현
안녕하세요 개인 프로젝트로 소셜 로그인을 구현 중에 있는데 제가 생각한 방법이 맞는지 의문이 들어서 여쭤봅니다
Springboot SpringSecurity+JWT+OAuth2로 소셜 로그인 구현을 진행하고 있습니다.
프론트 화면 단은 구현 안하고, 백엔드 서버 구현에만 집중을 하고 있습니다.
일반 회원의 회원가입, 로그인 후 스프링시큐리티와 JWT 통해 Access, Refresh 토큰 발급하는 것까진 진행을 했는데,
카카오 로그인 및 소셜 로그인에서 조금 막막해서 질문 드립니다ㅠ
http://localhost:1224/oauth2/authorization/kakao
이 주소로 요청을 하면, CustomOAuth2UserService 클래스에서 유저 정보를 저장하고,
OAuth2LoginSuccessHandler 클래스를 통해 JWT Access, Refresh 토큰을 발행해서
쿼리 파라미터로 넘겨주는 로직으로 진행을 했는데 이렇게 해도 괜찮은 걸까요?
잘하고 있는건지 감이 잘 안와서 여쭤봅니다
로그인에 성공한다면 이런 식으로 리다이렉트 해줍니다…
http://localhost:1224/?loginSuccess=true&accessToken=액세스토큰&refreshToken=리프레시토큰
그 이후에 게시글 작성이나 다른 비즈니스 관련 서비스들은 이 쿼리 파라미터의 액세스 토큰이 필요하구요
CustomOAuth2UserService 일부
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//DefaultOAuth2User 서비스를 통해 User 정보를 가져와야 하기 때문에 대리자 생성
OAuth2User oAuth2User = super.loadUser(userRequest);
//서비스를 구분해주는 코드 (구글/네이버/카카오)
String registrationId = userRequest.getClientRegistration().getRegistrationId();
//OAuth2 로그인 진행시 키가 되는 필드값 프라이머리키와 같은 값 (네이버 카카오 지원 x)
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
//OAuth2UserService를 통해 가져온 데이터를 담을 클래스
OAuthAttributes attributes = OAuthAttributes.of(
registrationId
, userNameAttributeName
, oAuth2User.getAttributes());
//로그인 한 유저 정보
Member member = saveOrUpdate(attributes);
//httpSession의 유저 속성을 설정
httpSession.setAttribute("user", new SessionUser(member));
// 로그인한 유저를 리턴함
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
// 유저를 저장하고, 이미 있는 데이터라면 업데이트 처리하는 메서드
private Member saveOrUpdate(OAuthAttributes attributes) {
Member user = memberRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getName()))
.orElse(attributes.toEntity());
return memberRepository.save(user);
}
}
OAuth2LoginSuccessHandler 일부
@Component
@Transactional
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
Map<String, Object> attributes = oauth2User.getAttributes();
Object id = attributes.get("id");
Object sub = attributes.get("sub");
String providerId = null;
if(id != null){
providerId = String.valueOf(id);
}else if(sub != null){
providerId = String.valueOf(sub);
}
Optional<Member> findMember = memberRepository.findByProviderId(providerId);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:1224");
if (findMember.isEmpty()) {
String redirectionUri = uriBuilder
.queryParam("loginSuccess", false)
.build()
.toUriString();
response.sendRedirect(redirectionUri);
} else {
Member member = findMember.get();
TokenResponse tokenResponse = jwtTokenProvider.createToken(authentication);
refreshTokenRepository.deleteAll();
// RefreshToken을 DB에 저장 (식별을 위해 memberId 함께 저장)
RefreshTokenEntity refreshToken = RefreshTokenEntity.builder()
.memberId(member.getId())
.token(tokenResponse.getRefreshToken())
.build();
refreshTokenRepository.save(refreshToken);
String redirectionUri = uriBuilder
.queryParam("loginSuccess", true)
.queryParam("accessToken", tokenResponse.getAccessToken())
.queryParam("refreshToken", tokenResponse.getRefreshToken())
.build()
.toUriString();
response.sendRedirect(redirectionUri);
}
}
}