Programming/Spring

OAuth2(3) OAuth2 Client 라이브러리 사용 카카오 로그인

사랑우주인 2022. 1. 20. 10:51

application-oauth.yml

spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-id: "Kakao Developers에서 받은 client-id "
            client-secret: "Kakao Developers에서 받은 client-secret"
            redirect-uri: "Kakao Developers에서 지정한 redirect-uri "
            authorization-grant-type: authorization_code
            client-authentication-method: POST
            client-name: Kakao

CustomOAuth2UserService

DefaultOAuth2UserService을 상속 받은 커스텀 클래스.  loadUser라는 메소드에 의해 후처리가 일어난다. userRequest에는 프로필 정보가 들어있다.

@Service
@Slf4j
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    private final AccountRepository accountRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2User oAuth2User = super.loadUser(userRequest);

        return processOAuth2User(userRequest, oAuth2User);
    }

    private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {

        OAuth2UserInfo oAuth2UserInfo = null;
        if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
            System.out.println("구글 로그인 요청");
            oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
        } else if (userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
            System.out.println("페이스북 로그인 요청");
            oAuth2UserInfo = new FaceBookUserInfo(oAuth2User.getAttributes());
        } else if (userRequest.getClientRegistration().getRegistrationId().equals("github")) {
            System.out.println("깃허브 로그인 요청");
            oAuth2UserInfo = new GitHubUserInfo(oAuth2User.getAttributes());
        } else if (userRequest.getClientRegistration().getRegistrationId().equals("kakao")) {
            System.out.println("카카오 로그인 요청");
            oAuth2UserInfo = new KakaoUserInfo(oAuth2User.getAttributes());
        } else if (userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
            System.out.println("네이버 로그인 요청");
            oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
        } else {
            System.out.println("우리는 구글과 페이스북만");
        }

        Optional<Account> userOptional =
                accountRepository.findByProviderAndProviderId(oAuth2UserInfo.getProvider(), oAuth2UserInfo.getProviderId());

        Account user;

        if (userOptional.isPresent()) {
            user = userOptional.get();
            // user가 존재하면 update 해주기
            user.setEmail(oAuth2UserInfo.getEmail());
            accountRepository.save(user);
        } else {
            // user의 패스워드가 null이기 때문에 OAuth 유저는 일반적인 로그인을 할 수 없음.
            user = Account.builder()
                    .nickname(oAuth2UserInfo.getProvider() + "_" + oAuth2UserInfo.getProviderId())
                    .email(oAuth2UserInfo.getEmail())
                    .name(oAuth2UserInfo.getName())
                    .password(bCryptPasswordEncoder.encode("oauth2_password"))
                    .roles("USER")
                    .provider(oAuth2UserInfo.getProvider())
                    .providerId(oAuth2UserInfo.getProviderId())
                    .build();
            accountRepository.save(user);
        }

        return new UserAccount(user, oAuth2User.getAttributes());
    }

}

OAuth2SuccessHandler

AuthenticationSuccessHandler을 상속 받은 커스텀 클래스. RedirectStrategy을 이용해 소셜 로그인 성공 후 이동할 url을 지정한다. 

@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
    private final JWTUtil jwtUtil;
    private final ObjectMapper objectMapper;
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    protected Log logger = LogFactory.getLog(this.getClass());



    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {

//        SecurityContextHolder.getContext().setAuthentication(authentication);


        response.addHeader(JWTUtil.AUTHORIZATION_HEADER, JWTUtil.BEARER + jwtUtil.generate(authentication));
        handle(request, response, authentication);
        clearAuthenticationAttributes(request);


    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN);
    }


    protected void handle(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication
    ) throws IOException {

        String targetUrl = determineTargetUrl(authentication);

        if (response.isCommitted()) {
            logger.debug(
                    "Response has already been committed. Unable to redirect to "
                            + targetUrl);
            return;
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(final Authentication authentication) {

        Map<String, String> roleTargetUrlMap = new HashMap<>();
        roleTargetUrlMap.put("ROLE_USER", "/");
        roleTargetUrlMap.put("ROLE_ADMIN", "/");

        final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (final GrantedAuthority grantedAuthority : authorities) {
            String authorityName = grantedAuthority.getAuthority();
            if(roleTargetUrlMap.containsKey(authorityName)) {
                return roleTargetUrlMap.get(authorityName);
            }
        }

        throw new IllegalStateException();
    }

    protected void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }



}

SecurityConfig

CustomOAuth2UserService와 OAuth2SuccessHandler을 설정 메소드에 등록한다.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JWTLoginFilter jwtLoginFilter = new JWTLoginFilter(objectMapper, jwtUtil, accountService, authenticationManager());
        JWTCheckFilter jwtCheckFilter = new JWTCheckFilter(authenticationManager(), accountService, jwtUtil);

        http.
                csrf().disable()
                .authorizeRequests().antMatchers("/", "/css/**", "/img/**", "/js/**", "/h2-console/**").permitAll()
                .and()
                .formLogin(
                        config -> {
                            config.loginPage("/login")
                                    .successForwardUrl("/")
                                    .failureForwardUrl("/login?error=true");
                        }
                )
                .addFilter(jwtLoginFilter)
                .addFilter(jwtCheckFilter)

                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()



                .oauth2Login(oauth -> {
                    oauth
                            .userInfoEndpoint(userinfo -> {
                                userinfo.userService(customOAuth2UserService);

                            })
                            .successHandler(oAuth2SuccessHandler)
                            .loginPage("/login");
                });

    }

참고

https://www.baeldung.com/spring_redirect_after_login