Skip to content

Commit

Permalink
Feat: 네이버 소셜 로그인 (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
leebuwon authored Feb 5, 2024
1 parent e9cb559 commit 0fa3904
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 1 deletion.
2 changes: 1 addition & 1 deletion backend/user-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// oauth 2.0 (추후)
// implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// smtp naver 이메일
implementation 'org.springframework.boot:spring-boot-starter-mail'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.tadak.userservice.global.oauth;

import com.tadak.userservice.domain.member.entity.Member;
import com.tadak.userservice.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final MemberRepository memberRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("loadUser 실행");
OAuth2UserService<OAuth2UserRequest, OAuth2User> service = new DefaultOAuth2UserService();
OAuth2User oAuth2User = service.loadUser(userRequest); // OAuth2 정보 가져오기

Map<String, Object> originAttributes = oAuth2User.getAttributes();

String socialName = userRequest.getClientRegistration().getRegistrationId(); // 소셜 정보 가져오기
log.info("socialName = {}", socialName);

OAuthAttributes attributes = OAuthAttributes.of(socialName, originAttributes);
Member member = getOrCreate(attributes); // get Or create
String email = member.getEmail();

// 권한 저장
List<GrantedAuthority> authorities = getGrantedAuthorities(member);

return new OAuth2CustomMember(socialName, originAttributes, authorities, email);
}

private Member getOrCreate(OAuthAttributes attributes){
Optional<Member> existingMember = memberRepository.findByEmail(attributes.getEmail());

if (existingMember.isPresent()) {
return existingMember.get();
}

Member newMember = attributes.toEntity();
return memberRepository.save(newMember);

}

private List<GrantedAuthority> getGrantedAuthorities(Member member) {
return Collections.singletonList(
new SimpleGrantedAuthority(member.getRole().name())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tadak.userservice.global.oauth;

import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

@AllArgsConstructor
public class OAuth2CustomMember implements OAuth2User, Serializable {
private String registrationId;
private Map<String, Object> attributes;
private List<GrantedAuthority> authorities;
private String email;

@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public String getName() {
return this.registrationId;
}

public String getEmail() {
return email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.tadak.userservice.global.oauth;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tadak.userservice.domain.member.dto.response.TokenResponseDto;
import com.tadak.userservice.domain.member.entity.Member;
import com.tadak.userservice.domain.member.repository.MemberRepository;
import com.tadak.userservice.global.jwt.filter.JwtFilter;
import com.tadak.userservice.global.jwt.provider.TokenProvider;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

@Component
@RequiredArgsConstructor
@Slf4j
public class OAuth2MemberSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final TokenProvider tokenProvider;
private final MemberRepository memberRepository;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("onAuthenticationSuccess 실행");
OAuth2CustomMember oAuth2User = (OAuth2CustomMember) authentication.getPrincipal();

String email = oAuth2User.getEmail();

Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다."));

List<GrantedAuthority> authorities = getGrantedAuthorities(member);

String accessToken = tokenProvider.createAccessToken(authentication);
String refreshToken = tokenProvider.createRefreshToken(authentication);

TokenResponseDto tokenResponseDto = tokenProvider.createTokenResponseDto(accessToken, refreshToken);

createResponseHeader(response, tokenResponseDto);
}

private void createResponseHeader(HttpServletResponse response, TokenResponseDto tokenResponseDto) throws IOException {
response.addHeader(JwtFilter.ACCESS_AUTHORIZATION_HEADER, "Bearer " + tokenResponseDto.getAccessToken());
response.addHeader(JwtFilter.REFRESH_AUTHORIZATION_HEADER, "Bearer " + tokenResponseDto.getRefreshToken());
new ObjectMapper().writeValue(response.getWriter(), tokenResponseDto);
response.flushBuffer();
}

private List<GrantedAuthority> getGrantedAuthorities(Member member) {
return Collections.singletonList(
new SimpleGrantedAuthority(member.getRole().name())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.tadak.userservice.global.oauth;

import com.tadak.userservice.domain.member.entity.Member;
import com.tadak.userservice.domain.member.entity.Role;
import com.tadak.userservice.domain.member.entity.State;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;

@Getter
@ToString
@AllArgsConstructor
@Builder
public class OAuthAttributes {

private Map<String, Object> attributes;
private String nameAttributesKey;
private String name;
private String email;

public static OAuthAttributes of(String socialName, Map<String, Object> attributes){
if ("naver".equalsIgnoreCase(socialName)){
return ofNaver("id", attributes);
}

throw new IllegalArgumentException("잘못된 소셜 정보입니다.");
}

private static OAuthAttributes ofNaver(String id, Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");

return OAuthAttributes.builder()
.name(String.valueOf(response.get("nickname")))
.email(String.valueOf(response.get("email")))
.attributes(response)
.nameAttributesKey(id)
.build();
}

public Member toEntity() {
return Member.builder()
.username(name)
.email(email)
.state(State.ACTIVE)
.role(Role.ROLE_USER)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.tadak.userservice.global.jwt.handler.JwtAccessDeniedHandler;
import com.tadak.userservice.global.jwt.handler.JwtAuthenticationEntryPoint;
import com.tadak.userservice.global.jwt.provider.TokenProvider;
import com.tadak.userservice.global.oauth.CustomOAuth2UserService;
import com.tadak.userservice.global.oauth.OAuth2MemberSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -29,6 +31,8 @@ public class SecurityConfig {
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final CustomOAuth2UserService customOAuth2UserService;
private final OAuth2MemberSuccessHandler oAuth2MemberSuccessHandler;

@Bean
public PasswordEncoder passwordEncoder() {
Expand All @@ -50,6 +54,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
.requestMatchers("/user-service/authcode/**").permitAll()
.requestMatchers("/oauth2/**").permitAll() // 네이버 로그인
.anyRequest().authenticated())
.oauth2Login(
oauth -> oauth
.userInfoEndpoint(config ->
config.userService(customOAuth2UserService))
.successHandler(oAuth2MemberSuccessHandler))

.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // session 사용 x
Expand Down

0 comments on commit 0fa3904

Please sign in to comment.