Spring Security + jwt + OAuth2(소셜 로그인 2편)

2024. 6. 30. 21:15·Backend/프로젝트
목차
  1. application.yml 설정 추가
  2. gradle 설정 추가
  3. SecurityConfig 설정 추가
  4. CustomOAuth2UserService 구현
  5. OAuth2SuccessHandler 구현
  6. OAtuhAttributes 구현
  7. 동작 확인
  8. 총정리
반응형

application.yml 설정 추가

spring:
    security:
        oauth2:
          client:
            registration:
              google:
                client-id: 
                client-secret:
                scope:
                  - email
              naver:
                client-id: 
                client-secret: 
                scope:
                  - nickname
                  - email
                client-name: Naver
                authorization-grant-type: authorization_code
                redirect-uri: http://localhost:8080/login/oauth2/code/naver
            provider:
              naver:
                authorization-uri: https://nid.naver.com/oauth2.0/authorize
                token-uri: https://nid.naver.com/oauth2.0/token
                user-info-uri: https://openapi.naver.com/v1/nid/me
                user-name-attribute: response

1편에서 구글, 네이버 설정 후 얻은 client-id와 client-secret를 넣어줍니다.

 

gradle 설정 추가

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

 

SecurityConfig 설정 추가

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtProvider jwtProvider;
    private final CustomOAuth2UserService customOAuth2UserService;
    private final OAuth2SuccessHandler oAuth2SuccessHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf().disable()
                .headers().frameOptions().sameOrigin().and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()

                ....
                ..//설정
                ....

                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .and()
                .oauth2Login()
                .successHandler(oAuth2SuccessHandler)
                    .userInfoEndpoint()
                    .userService(customOAuth2UserService)
                .and()

                .and()
                .exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())

                .and()
                .addFilterBefore(new JwtFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

 

successHandler(oAuth2SuccessHandler) : Oauth2 로그인 성공시 Oauth2SuccessHandler를 호출한다. (인증이 성공한 후의 작업)

.userInfoEndpoint() : 인증 제공자로부터 사용자 정보를 가져오는 엔드포인트 설정

.userService(customOauth2UserService) : 사용자 정보를 가져오는 서비스를 설정

 

CustomOAuth2UserService 구현

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        String registrationId = userRequest.getClientRegistration().getRegistrationId();

        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();

        Map<String, Object> attributes = oAuth2User.getAttributes();
        UserProfile userProfile = OauthAttributes.extract(registrationId, attributes);
        User socialUser = saveOrUpdateUserProfile(userProfile);

        return new CustomOAuth2User(socialUser, attributes, userNameAttributeName);
    }

    private User saveOrUpdateUserProfile(UserProfile userProfile) {
        return userRepository.findByEmail(userProfile.getEmail())
                .orElseGet(() -> userRepository.save(userProfile.toEntity()));
    }
}

 

OAuth2User oAuth2User = super.loadUser(userRequest) : OAuth2인증된 사용자의 정보를 담고 있는 OAuth2User 객체를 가져온다.

 

String registrationId = userRequest.getClientRegistration().getRegistrationId() : 요청된 클라이언트의 등록 ID를 가져온다. 어떤 OAuth2(구글, 네이버)가 사용되었는지 식별하는데 사용된다.

 

String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName()

: OAuth2제공자가 사용자 정보를 제공할 때 사용하는 사용자 이름 속성을 가져온다. ex) 구글(sub)

 

Map<String, Object> attributes = oAuth2User.getAttributes() : OAuth2 서비스 유저의 정보들을 Map형태로 가져온다.

 

UserProfile userProfile = OauthAttributes.extract(registrationId, attributes) : registrationId에(구글, 네이버) 따라 유저 정보를 통해 공통된 UserProfile 객체를 만들어준다.


User socialUser = saveOrUpdateUserProfile(userProfile) : 사용자가 존재하면 유저정보를 업데이트하고 존재하지 않으면 DB에 해당 유저를 저장한다.

 

OAuth2SuccessHandler 구현

@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        User socialUser = ((CustomOAuth2User) authentication.getPrincipal()).getSocialUser();
        String token = jwtProvider.createAccessToken(socialUser.getEmail(), socialUser.getUserType());

        response.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
        response.setStatus(HttpStatus.OK.value());
    }
}

인증이 성공하면 해당 OAuth2SuccessHandler가 실행되면서 헤더에 토큰을 담아 응답을 내려줍니다.

 

OAtuhAttributes 구현

public enum OauthAttributes {

    GOOGLE("google", attributes -> new UserProfile(
            (String) attributes.get("email"),
            "Social" + UUID.randomUUID() //구글 닉네임은 임시로 값 부여...ㅠ 추후 수정
    )),

    NAVER("naver", attributes -> {
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");
        return new UserProfile(
                (String) response.get("email"),
                (String) response.get("nickname")
        );
    });

    private final String registrationId;
    private final Function<Map<String, Object>, UserProfile> userProfileFactory;

    OauthAttributes(String registrationId, Function<Map<String, Object>, UserProfile> userProfileFactory) {
        this.registrationId = registrationId;
        this.userProfileFactory = userProfileFactory;
    }

    public static UserProfile extract(String registrationId, Map<String, Object> attributes) {
        return Arrays.stream(values())
                .filter(provider -> registrationId.equals(provider.registrationId))
                .findFirst()
                .orElseThrow(IllegalArgumentException::new)
                .userProfileFactory.apply(attributes);
    }
}

OAuth2 제공자에 따라 UserProfile 객체를 반환한다.

동작 확인

시큐리티에서 기본적으로 제공하는 URL을 통해 따로 컨트롤러를 만들지 않고

http://localhost:8080/oauth2/authorization/naver 로 요청을 보냅니다.

정상적으로 헤더에 토큰을 담아서 응답해주는 것을 볼 수 있습니다. 

구글 로그인도 정상적으로 작동하는지 확인해봅시다.

http://localhost:8080/oauth2/authorization/google

구글 로그인도 정상적으로 토큰이 발급되는 것을 볼 수 있습니다.

 

총정리

 

1. 사용자가 로그인 버튼을 클릭하면 OAuth2 제공자의 인증페이지로 리다이렉트한다.

2. 로그인 페이지에서 인증을 수행한다.

3. 인증에 성공하면 애플리케이션으로 인증 코드를 반환한다.

4. 애플리케이션은 인증 코드를 통해 OAuth2 제공자로부터 액세스 토큰을 요청한다.

5. OAuth2 제공자는 애플리케이션에 access token을 반환한다.

6. 애플리케이션은 access token을 통해 OAuth2 제공자로부터 사용자 정보를 받아온다.

7. CustomOAuth2UserService를 통해 받아온 사용자 정보를 기반으로 유저를 create or update 한다.

8. 해당과정이 성공적으로 수행되면 토큰을 생성해 헤더에 담아 응답한다.

 

반응형

'Backend > 프로젝트' 카테고리의 다른 글

QueryDsl을 활용한 카테고리별 조회(페이징) 기능 만들기  (1) 2024.07.30
Github Actions를 이용한 CI 구성  (1) 2024.07.10
OAuth란 무엇인가? (소셜 로그인 1편)  (1) 2024.06.17
모니터링 시스템 구축(1) Prometheus, Actuator 구성하기  (0) 2024.06.13
모니터링 시스템 구축(2) Grafana 대시보드 구성하기  (0) 2024.06.01
  1. application.yml 설정 추가
  2. gradle 설정 추가
  3. SecurityConfig 설정 추가
  4. CustomOAuth2UserService 구현
  5. OAuth2SuccessHandler 구현
  6. OAtuhAttributes 구현
  7. 동작 확인
  8. 총정리
'Backend/프로젝트' 카테고리의 다른 글
  • QueryDsl을 활용한 카테고리별 조회(페이징) 기능 만들기
  • Github Actions를 이용한 CI 구성
  • OAuth란 무엇인가? (소셜 로그인 1편)
  • 모니터링 시스템 구축(1) Prometheus, Actuator 구성하기
여포개발자
여포개발자
어제보다 오늘 더여포개발자 님의 블로그입니다.
여포개발자
어제보다 오늘 더
여포개발자
전체
오늘
어제
  • 분류 전체보기 (141)
    • Backend (41)
      • 프로젝트 (18)
      • MSA 전환 (10)
      • spring (6)
      • JPA (7)
    • JAVA (11)
    • Kotlin 정리 (11)
    • 알고리즘 (59)
      • 프로그래머스 LV0 (5)
      • 프로그래머스 LV1 (12)
      • 프로그래머스 LV2 (17)
      • 프로그래머스 LV3 (8)
      • 백준 (14)
      • 소프티어 (3)
    • 네트워크 (3)
    • Docker (3)
    • SQL (5)
    • Kafka (6)
    • 일상 (1)
    • .NET (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바 #백준
  • TroubleShooting #JPA
  • #프로그래머스 #자바
  • HTTP
  • 백준
  • 티스토리챌린지
  • Kotlin
  • MSA
  • #프로그래머스
  • #JAVA #프로그래머스 #LV1
  • 프로그래머스
  • JAVA #프로그래머스 #LV0
  • #JAVA #프로그래머스
  • #JAVA #프로그래머스 #LV1 #모두화이팅
  • java
  • 네트워크
  • Kotiln
  • Spring
  • #JPA #JAVA
  • docker #MySQL
  • 모니터링
  • 프로젝트
  • 오블완
  • docker
  • JPA
  • 프로그래머스LV1

최근 댓글

최근 글

반응형
hELLO· Designed By정상우.v4.5.2
여포개발자
Spring Security + jwt + OAuth2(소셜 로그인 2편)
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.