본문 바로가기
개발(라이브러리,프레임워크)/Spring boot

백엔드 Oauth SNS로그인 구현하기

by zieunee 2021. 6. 28.
반응형

백엔드에서 Oauth SNS로그인 구현하기

 

사실상 나머지는 빈껍데기고 카카오톡만 구현했음

 

OauthController.java

@RestController
@CrossOrigin
@RequiredArgsConstructor
@RequestMapping(value = "/auth")
@Slf4j
public class OauthController {
    private final OauthService oauthService;

    /**
     * 사용자로부터 SNS 로그인 요청을 Social Login Type 을 받아 처리
     * @param socialLoginType (GOOGLE, KAKAO)
     */
    @GetMapping(value = "/{socialLoginType}")
    public void socialLoginType(
            @PathVariable(name = "socialLoginType") SocialLoginType socialLoginType) {
        log.info(">> 로그인 요청 {} Social Login", socialLoginType);
        oauthService.request(socialLoginType);
    }

    /**
     * Social Login API Server 요청에 의한 callback 을 처리
     * @param socialLoginType (GOOGLE, KAKAO)
     * @param code API Server 로부터 넘어오는 code
     * @return SNS Login 요청 결과로 받은 Json 형태의 String 문자열 (access_token, refresh_token 등)
     */
    @GetMapping(value = "/{socialLoginType}/callback")
    public String callback(
            @PathVariable(name = "socialLoginType") SocialLoginType socialLoginType,
            @RequestParam(name = "code") String code) {
        log.info(">> 로그인 code :: {}", code);
        return oauthService.requestAccessToken(socialLoginType, code);
    }
}

OauthService.java

@Service
@RequiredArgsConstructor
public class OauthService {
    private final List<SocialOauth> socialOauthList;
    private final HttpServletResponse response;

    public void request(SocialLoginType socialLoginType) {
        SocialOauth socialOauth = this.findSocialOauthByType(socialLoginType);
        String redirectURL = socialOauth.getOauthRedirectURL();
        System.out.println("\n>>>url:::");
        System.out.println(redirectURL);
        System.out.println(">>>url:::\n");
        try {
            response.sendRedirect(redirectURL);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String requestAccessToken(SocialLoginType socialLoginType, String code) {
        SocialOauth socialOauth = this.findSocialOauthByType(socialLoginType);
        return socialOauth.requestAccessToken(code);
    }

    private SocialOauth findSocialOauthByType(SocialLoginType socialLoginType) {
        return socialOauthList.stream()
                .filter(x -> x.type() == socialLoginType)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("알 수 없는 socialType"));
    }
}

SocialLoginType.java

public enum SocialLoginType {
    GOOGLE,
    KAKAO
}

SocialLoginTypeConverter.java

@Configuration
public class SocialLoginTypeConverter implements Converter<String, SocialLoginType> {
    @Override
    public SocialLoginType convert(String s) {
        return SocialLoginType.valueOf(s.toUpperCase());
    }
}

KakaoInfo.java

@Entity
@Getter
@Data
@ToString
@NoArgsConstructor
public class KakaoInfo {
    @Id
    private String access_token;
    private String token_type;
    private String refresh_token;
    long expires_in;
    long  refresh_token_expires_in;


    @Builder
    public KakaoInfo(String access_token, String token_type, String refresh_token, long expires_in, long refresh_token_expires_in) {
        this.access_token = access_token;
        this.token_type = token_type;
        this.refresh_token = refresh_token;
        this.expires_in = expires_in;
        this.refresh_token_expires_in = refresh_token_expires_in;
    }


}

SocialOauth.java

@Component
@RequiredArgsConstructor
public class GoogleOauth implements SocialOauth {
    // @Value("${spring.social.google.url.login}")
    // private String GOOGLE_SNS_BASE_URL;
    // @Value("${spring.social.google.client.id}")
    // private String GOOGLE_SNS_CLIENT_ID;
    // @Value("${spring.social.google.url.callback}")
    // private String GOOGLE_SNS_CALLBACK_URL;
    // @Value("${spring.social.google.client.secret}")
    // private String GOOGLE_SNS_CLIENT_SECRET;
    // @Value("${spring.social.google.url.token}")
    // private String GOOGLE_SNS_TOKEN_BASE_URL;
    //applicaion.yml에서 갖고와야함     

    private String GOOGLE_SNS_BASE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
    private String GOOGLE_SNS_CLIENT_ID = "334094510084-mhh7m5e2ienuo9v47mcltukvj9fgddt2.apps.googleusercontent.com";
    private String GOOGLE_SNS_CALLBACK_URL = "http://localhost:8080/loginservice/auth/google/callback";
    private String GOOGLE_SNS_CLIENT_SECRET = "GQ3DZuC966U17oc8SlnsV_Xq";
    private String GOOGLE_SNS_TOKEN_BASE_URL = "https://oauth2.googleapis.com/token";

    @Override
    public String getOauthRedirectURL() {
        Map<String, Object> params = new HashMap<>();
        params.put("scope", "profile");
        params.put("response_type", "code");
        params.put("client_id", GOOGLE_SNS_CLIENT_ID);
        params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL);

        String parameterString = params.entrySet().stream()
                .map(x -> x.getKey() + "=" + x.getValue())
                .collect(Collectors.joining("&"));

        return GOOGLE_SNS_BASE_URL + "?" + parameterString;
    }

    @Override
    public String requestAccessToken(String code) {
        RestTemplate restTemplate = new RestTemplate();

        Map<String, Object> params = new HashMap<>();
        params.put("code", code);
        params.put("client_id", GOOGLE_SNS_CLIENT_ID);
        params.put("client_secret", GOOGLE_SNS_CLIENT_SECRET);
        params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL);
        params.put("grant_type", "authorization_code");

        ResponseEntity<String> responseEntity =
                restTemplate.postForEntity(GOOGLE_SNS_TOKEN_BASE_URL, params, String.class);

        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            System.out.println("\n>>>> getKakaoAccessToken"); 
            System.out.println(responseEntity.getBody());
            System.out.println("\n"); 
            return responseEntity.getBody();
        }
        return "구글 로그인 요청 처리 실패";
    }

    public String requestAccessTokenUsingURL(String code) {
        try {
            URL url = new URL(GOOGLE_SNS_TOKEN_BASE_URL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setDoOutput(true);

            Map<String, Object> params = new HashMap<>();
            params.put("code", code);
            params.put("client_id", GOOGLE_SNS_CLIENT_ID);
            params.put("client_secret", GOOGLE_SNS_CLIENT_SECRET);
            params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL);
            params.put("grant_type", "authorization_code");

            String parameterString = params.entrySet().stream()
                    .map(x -> x.getKey() + "=" + x.getValue())
                    .collect(Collectors.joining("&"));

            BufferedOutputStream bous = new BufferedOutputStream(conn.getOutputStream());
            bous.write(parameterString.getBytes());
            bous.flush();
            bous.close();

            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            StringBuilder sb = new StringBuilder();
            String line;

            while ((line = br.readLine()) != null) {
                sb.append(line);
            }

            if (conn.getResponseCode() == 200) {
                return sb.toString();
            }
            return "구글 로그인 요청 처리 실패";
        } catch (IOException e) {
            throw new IllegalArgumentException("알 수 없는 구글 로그인 Access Token 요청 URL 입니다 :: " + GOOGLE_SNS_TOKEN_BASE_URL);
        }
    }

    public void getProfile(String accessToken) {
        //구글 정보 가져와야하는데 아직 못함 
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.set("Authorization", "Bearer " + accessToken);

        // Set http entity
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
        try { // /auth/userinfo
            // Request profile
            String url = "https://www.googleapis.com/auth/userinfo.email"; //???
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            System.out.println("\n>>>>getKakaoProfile"); 
            System.out.println(response.getBody());
            System.out.println("\n"); 

        } catch (Exception e) {
            e.printStackTrace();
        }
          //  return null;
    }
}

SocialOauth.java

@Component
@RequiredArgsConstructor
public class KakaoOauth implements SocialOauthSocialOauth {

    // @Value("${spring.social.kakao.url.login}")
    // private String KAKAO_SNS_BASE_URL;
    // @Value("${spring.social.kakao.client.id}")
    // private String KAKAO_SNS_CLIENT_ID;
    // @Value("${spring.social.kakao.url.callback}")
    // private String KAKAO_SNS_CALLBACK_URL;
    // @Value("${spring.social.kakao.client.secret}")
    // private String KAKAO_SNS_CLIENT_SECRET;
    // @Value("${spring.social.kakao.url.login}")
    // private String KAKAO_SNS_TOKEN_BASE_URL;
    //applicaion.yml에서 갖고와야함 

    private String KAKAO_SNS_BASE_URL = "https://kauth.kakao.com/oauth/authorize";
    private String KAKAO_SNS_CLIENT_ID = "a33afbe682420249e0f92f9fa81af07d";
    private String KAKAO_SNS_CALLBACK_URL = "http://localhost:8080/loginservice/auth/kakao/callback";
    //private String KAKAO_SNS_CLIENT_SECRET;
    private String KAKAO_SNS_TOKEN_BASE_URL = "https://kauth.kakao.com/oauth/token";
    private String KAKAO_SNS_PROFILE_BASE_URL = "https://kapi.kakao.com/v2/user/me";

    @Override
    public String getOauthRedirectURL() {
        Map<String, Object> params = new HashMap<>();
    //    params.put("scope", "profile");
        params.put("response_type", "code");
        params.put("client_id", KAKAO_SNS_CLIENT_ID);
        params.put("redirect_uri", KAKAO_SNS_CALLBACK_URL);

        String parameterString = params.entrySet().stream()
                .map(x -> x.getKey() + "=" + x.getValue())
                .collect(Collectors.joining("&"));

        return KAKAO_SNS_BASE_URL + "?" + parameterString;

    }

    @Override
    public String requestAccessToken(String code) {

        RestTemplate restTemplate = new RestTemplate();
        Gson gson = new Gson();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // Set parameter
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "authorization_code");
        params.add("client_id", KAKAO_SNS_CLIENT_ID);
        params.add("redirect_uri",KAKAO_SNS_CALLBACK_URL);
        params.add("code", code);
        // Set http entity
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
        try {
            String tokenUrl = KAKAO_SNS_TOKEN_BASE_URL;
            ResponseEntity<String> response = restTemplate.postForEntity(tokenUrl, request, String.class);

            System.out.println("\n>>>> getKakaoAccessToken"); 
            System.out.println(response.getBody());
            System.out.println("\n"); 

            if (response.getStatusCode() == HttpStatus.OK) {
                String str = response.getBody();
                ObjectMapper mapper = new ObjectMapper();
                Map<String, Object> map = mapper.readValue(str, Map.class);
                Object obj = map.get("access_token");
                String resString = String.valueOf(obj);
                System.out.println("\n accessToken");
                System.out.println(resString);
                //카카오에서 개인정보 가져오기 (이름 이멜)
                getProfile(resString);

                return response.getBody();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "카카오 로그인 요청 처리 실패";
    }

    public void getProfile(String accessToken) {
        RestTemplate restTemplate = new RestTemplate();
        Gson gson = new Gson();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.set("Authorization", "Bearer " + accessToken);

        // Set http entity
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
        try {
            // Request profile
            String url = KAKAO_SNS_PROFILE_BASE_URL;
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            System.out.println("\n>>>>getKakaoProfile"); 
            System.out.println(response.getBody());
            System.out.println("\n"); 
            String str = response.getBody();
            System.out.println(str);

            // ObjectMapper mapper = new ObjectMapper();
            // Map<String, Object> map = mapper.readValue(str, Map.class);
            // Object obj = map.get("kakao_account");
            // String resString = String.valueOf(obj);

           // if (response.getStatusCode() == HttpStatus.OK)
              //  return gson.fromJson(response.getBody(), UserInfo.class);


        } catch (Exception e) {
            e.printStackTrace();
        }
          //  return null;
    }
}

SocialOauth.java

public interface SocialOauth {
    /**
     * 각 Social Login 페이지로 Redirect 처리할 URL Build
     * 사용자로부터 로그인 요청을 받아 Social Login Server 인증용 code 
     */
    String getOauthRedirectURL();

    /**
     * API Server로부터 받은 code를 활용하여 사용자 인증 정보 요청
     * @param code API Server 에서 받아온 code
     * @return API 서버로 부터 응답받은 Json 형태의 결과를 string으로 반
     */
    String requestAccessToken(String code);

    default SocialLoginType type() {
         if (this instanceof GoogleOauth) {
            return SocialLoginType.GOOGLE;
        } else if (this instanceof KakaoOauth) {
            return SocialLoginType.KAKAO;
        } else {
            return null;
        }
    }
}
반응형