나와바리 - Spring Security + JWT를 이용한 로그인 API 구현(4)

Sungmin·2023년 5월 12일
0

OAuth2인증과정

CustomOAuth2User 생성

    private String email;
    private Role role;


    public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
                            Map<String, Object> attributes, String nameAttributeKey,
                            String email, Role role) {
        super(authorities, attributes, nameAttributeKey);
        this.email = email;
        this.role = role;
    }

Resource Server에서 제공하지 않는 추가 정보들을 내 서비스에서 가지고 있기 위해 생성함. ex) 구역설정 x = GUEST, 구역설정 o = MEMBER

OAuth DTO클래스 - OAuthAttributes

public static OAuthAttributes of(SocialType socialType,
                                     String userNameAttributeName, Map<String, Object> attributes) {
        if (socialType == SocialType.KAKAO){}
        return ofKakao(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .nameAttributeKey(userNameAttributeName)
                .oauth2UserInfo(new KakaoOAuth2UserInfo(attributes))
                .build();
    }
    
    public Member toEntity(SocialType socialType, OAuth2UserInfo oauth2UserInfo) {

        String email = oauth2UserInfo.getEmail();
        if (email == null) {
            email = UUID.randomUUID() + "@socialUser.com";
        }

        return Member.builder()
                .socialType(socialType)
                .kakao_id(oauth2UserInfo.getKakao_id())
                .email(email)
                .profile_nickname(oauth2UserInfo.getProfile_nickname())
                .profile_image(oauth2UserInfo.getProfile_image())
                .age(oauth2UserInfo.getAgeRange())
                .gender(oauth2UserInfo.getGender())
                .role(Role.GUEST)
                .build();
    }

SocialType kakao에서 데이터를 받아오도록 설정.
유저 정보가 OAuth2UserInfo에 담긴 상태에서 toEntity로 Member에 데이터를 빌드해줌.
email의 경우 없으면 UUID의 중복되지않는 랜덤값으로 설정하고 있으면 회원의 이메일로 빌드 됨.(이메일은 JWT Token을 발급하기 위한 용도)


카카오 scope에 등록된 값을 가져오기

public class KakaoOAuth2UserInfo extends OAuth2UserInfo {
    public KakaoOAuth2UserInfo(Map<String, Object> attributes) {
        super(attributes);
    }

    @Override
    public String getKakao_id() {
        return String.valueOf(attributes.get("id"));
    }

    @SuppressWarnings("unchecked")
    @Override
    public String getProfile_nickname() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        if (account == null) {
            return null;
        }
        Map<String, Object> profile = (Map<String, Object>) account.get("profile");
        if (profile == null) {
            return null;
        }

        return (String) profile.get("nickname");
    }
    @SuppressWarnings("unchecked")
    @Override
    public String getProfile_image() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        if (account == null) {
            return null;
        }
        Map<String, Object> profile = (Map<String, Object>) account.get("profile");
        if (profile == null) {
            return null;
        }

        return (String) profile.get("thumbnail_image_url");
    }

    @SuppressWarnings("unchecked")
    @Override
    public String getAgeRange() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        if (account == null) {
            return null;
        }
        String ageRange = (String) account.get("age_range");
        return ageRange;
    }

    @SuppressWarnings("unchecked")
    @Override
    public String getGender() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        if (account == null) {
            return null;
        }
        String gender = (String) account.get("gender");
        return gender;
    }

    @SuppressWarnings("unchecked")
    @Override
    public String getEmail() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        if (account == null) {
            return null;
        }
        String email = (String) account.get("email");
        return email;
    }

profile_nickname, profile_image, age_range, gender, email
각각 가져오는 방식이 조금씩 다른데 닉네임과 이미지는 profile에서 가져오고
그 외엔 kakao_account에서 받아오면 된다. 동의하지않았을 경우 null이 반환된다.


CustomOAuth2UserService

@Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("CustomOAuth2UserService.loadUser() 실행 - OAuth2 로그인 요청 진입");

       
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        SocialType socialType = getSocialType(registrationId);
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); 

        OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, attributes);

        Member createdUser = getMember(extractAttributes, socialType); 

      
        return new CustomOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())),
                attributes,
                extractAttributes.getNameAttributeKey(),
                createdUser.getEmail(),
                createdUser.getRole()
        );
    }

DefaultOAuth2UserService 객체를 생성하여, loadUser(userRequest)를 통해 DefaultOAuth2User 객체를 생성 후 반환.
OAuth2User는 OAuth 서비스에서 가져온 유저 정보를 담고 있는 유저
DefaultOAuth2User를 구현한 CustomOAuth2User 객체를 생성해서 반환

소셜 로그인 성공시 핸들러

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("OAuth2 Login 성공!");
        try {
            CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();

            String accessToken = jwtService.createAccessToken(oAuth2User.getEmail());
            String refreshToken = jwtService.createRefreshToken();

            response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);
            response.addHeader(jwtService.getRefreshHeader(), "Bearer " + refreshToken);

            memberRepository.saveRefreshToken(oAuth2User.getEmail(), refreshToken);

            jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken);

            // User의 Role이 GUEST일 경우 구역설정 페이지로 이동
            if(oAuth2User.getRole() == Role.GUEST) {


                response.sendRedirect("/login"); // 프론트의 구역 정보 입력 폼으로 리다이렉트

            } else {
                    response.sendRedirect("/main");
                }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

accessToken & refreshToken을 생성하여 헤더에 담아 보낸다. refreshToken은 DB에 저장 한다.
GUEST일 경우 구역입력 페이지로 넘어가게 된다.(임시로 "/login"해 놓았음)
MEMBER일 경우 메인페이지로 이동한다.(임시로 "/main"해 놓았음)

소셜로그인 실패시 핸들러

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.getWriter().write("소셜 로그인 실패! 서버 로그를 확인해주세요.");
        log.info("소셜 로그인에 실패했습니다. 에러 메시지 : {}", exception.getMessage());
    }
profile
Let's Coding

0개의 댓글