본문 바로가기

프로젝트

SpringBoot로 그룹캘린더 만들기 - Spring Security, OAuth2 사용하기 (네이버, 카카오 회원가입 및 로그인)

푸하하.. 약 일주일을 이 회원가입에 신경 쓰느라 블로그 포스팅을 못했는데.. (해결하면 써야지 하다가)

드디어 해결하고 컴백했다.. ^^...

 

우리는 <네이버> 회원가입 버튼을 클릭 -> 단두린더 회원가입 부가입력 -> 회원가입 성공! 이 되길 원했는데

알고보니 진짜 불필요한 부분이었다.

네이버에서 권장하지 않는 회원가입 폼이었음. 미쳤냐? ㅋㅋ 진짜 이거 가지고 일주일 싸운게 너무 한심 ..ㅠㅠㅠ 이거 진작 알았으면 바로 다른거 하는건데 ... 그래도 이거 씨름한다고 공부를 계속 계속해서 깊이 있게 알게되긴 했다.

 

 

 

스프링 시큐리티에서는 회원가입을 oauth2를 통해 지원을 하는데... 이것이 카카오와 네이버는 딱히 규정되어있지 않다. 그래서 application.yml에 적어서 스프링 시큐리티를 통해 회원가입을 진행해 줄 것이다.

뭐 시큐리티 안쓸거면 상관없는데 괜히 http 전송하고 결과 받고 하는 것 보다 나는 정해진 규격 정해서 폼 따라가는게 더 편하다고 생각

 

 

 

build.gradle
//스프링 시큐리티 관련
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation "io.jsonwebtoken:jjwt:0.9.1"
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'​

 

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          naver:
            client-id: {네이버}
            client-secret: {네이버}
            redirect-uri: "http://localhost:8089/login/oauth2/code/naver"
            authorization-grant-type: authorization_code
            scope: name, email, profile_image
            client-name: Naver
          kakao:
            client-id: {카카오}
            client-secret: {카카오}
            redirect-uri: "http://localhost:8089/login/oauth2/code/kakao"
            authorization-grant-type: authorization_code
            client-authentication-method: POST
            scope:
              - profile_nickname
              - profile_image
              - account_email
            client-name: Kakao
        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
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: id​

{} 중괄호 속에 있는 부분은 네이버와 카카오 애플리케이션 등록 부분에 가서 본인들 것으로 참조하시고

redirect-url과 authorization-url이 중요하다.

여기서 본인이 직접 구현해서 쓰겠다~ 하면은 알아서 다른 주소로 적으면 되고 스프링시큐리티 폼으로 갈거야! 하면은 저 형식으로 적어주면 된다.

공부하면서 내 블로그 글만 참조하지 않겠지만.. 네이버와 카카오는 데이터 받아올때 user-name-attribute 여기 위에 적어놓은 대로 적어줘야 한다!

 

 

SecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
    private final CustomOAuth2UserService customOAuth2UserService;
    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated().and()

                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                        UsernamePasswordAuthenticationFilter.class)

                .oauth2Login()
                .defaultSuccessUrl("/userGroup")

                .userInfoEndpoint()
                .userService(customOAuth2UserService)
        ;
        return http.build();
    }
}​

hahah.. 시큐리티도 참... 여기도 공부 많이 했던 부분인데 ㅠㅠ 흑흑

무튼 ouath2Login()  이 뒤만 참고해주면 되는데

 

defaultSuccessUrl : 인증 끝나고 나서 리턴할 곳이다! 나는 회원가입->로그인이 완료되면 유저생성할거니까 유저그룹으로

 

userInfoEndpoint() = oauth2 로그인 성공 후 사용자정보를 가져올때 설정담당

userService() = 소셜로그인성공후 후속조치를 진행할 userService 인터페이스의 구현제등록

 

 

디버깅 돌려본 결과... customOAuth2UserService의 loadUser --> defaultSuccessUrl 이렇게 흘러갔다.

로드유저가 돌아간 뒤 결과를 유저그룹으로 반환해준다~!

 

 

CustomOAuth2UserService
@Service
@Transactional
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> service = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = service.loadUser(userRequest); // Oath2 정보를 가져옴

        String registrationId = userRequest.getClientRegistration().getRegistrationId(); // 소셜 정보 가져옴
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        String userId = registrationId + "_" + attributes.getId();
        String access_token = userRequest.getAccessToken().getTokenValue();

        User user;
        Optional<User> optionalUser = userRepository.findByUserId(userId);

        if (optionalUser.isPresent()){ // 로그인
            user = optionalUser.get();
        }else{ // 회원가입
            user = User.builder()
                    .user_id(userId)
                    .user_name(attributes.getUser_name())
                    .email(attributes.getEmail())
                    .phone(attributes.getPhone())
                    .provider(registrationId)
                    .build();
            userRepository.saveUser(user);
        }

        httpSession.setAttribute("user", new SessionUser(user));
        httpSession.setAttribute("access_token", access_token);

        return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(Role.USER.getKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());
    }

}​
OAuthAttribute
@Getter
@Builder
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String id;
    private String user_name;
    private String email;
    private String phone;

    public static OAuthAttributes of(String socialName, String userNameAttributeName, Map<String, Object> attributes){
        if("kakao".equals(socialName)){
            return ofKakao(userNameAttributeName, attributes);
        }else if("naver".equals(socialName)) {
            return ofNaver(userNameAttributeName, attributes);
        }

        return null;
    }

    /**
     * 네이버 로그인
     * @param userNameAttributeName
     * @param attributes
     * @return
     */
    private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> response = (Map<String, Object>)attributes.get("response");

        return OAuthAttributes.builder()
                .id(String.valueOf(response.get("id")))
                .user_name(String.valueOf(response.get("name")))
                .email(String.valueOf(response.get("email")))
                .phone(String.valueOf(response.get("mobile")))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    /**
     * 카카오 로그인
     * @param userNameAttributeName
     * @param attributes
     * @return
     */
    private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> kakaoAccount = (Map<String, Object>)attributes.get("kakao_account");
        Map<String, Object> kakaoProfile = (Map<String, Object>)kakaoAccount.get("profile");

        return OAuthAttributes.builder()
                .id(String.valueOf(attributes.get(userNameAttributeName)))
                .user_name(String.valueOf(kakaoProfile.get("nickname")))
                .email(String.valueOf(kakaoAccount.get("email")))
                .nameAttributeKey(userNameAttributeName)
                .attributes(attributes)
                .build();
    }

}​
UserController
@Controller
public class UserController {
    // 그룹 조회
    @GetMapping("/userGroup")
    public String userGroup(HttpServletRequest request, Model model){
        HttpSession session = request.getSession();
        model.addAttribute("userInfo", session.getAttribute("user"));
        return "user/userGroup";
    }
}​

이런식으로... 흘러간다고...

결과적으로 httpSession에 담긴 유저값을 풀어서 화면에 넘겨주면 된다.