반응형
백엔드에서 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;
}
}
}
반응형
'개발(라이브러리,프레임워크) > Spring boot' 카테고리의 다른 글
로그인 세션관리 구현(Interceptor, jwt 토큰) (1) | 2021.07.03 |
---|---|
CORS 설정 (0) | 2021.07.02 |
로그인 세션관리 구현(Interceptor, jwt 토큰) (1) | 2021.06.28 |
vsCode 에서 JUnit Test (0) | 2021.05.26 |
JPA (0) | 2021.04.04 |