Intrager
38
2021-08-31 19:59:00 작성 2021-08-31 20:08:51 수정됨
9
692

spring으로 소셜 로그인 후 토큰을 받는 건 문제없지만 세션을 어떻게 추가해야될지 모르겠습니다ㅜㅜ


안녕하세요! 현재 팀 프로젝트로 백엔드 파트를 맡고 있고 spring boot로 구글 로그인 구현 중에 있는 대학생입니다.


메인 페이지(index)가 따로 구현되어있고 


프론트단에서 http://localhost:8888/auth/GOOGLE 로 들어가면 컨트롤러로 가게 되고, 컨트롤러에서 url값을 가져오도록 "클래스"로 보냅니다. 이 "클래스"에서 다시 컨트롤러로 url 값을 가지고 오게 되면 로그인을 하고, /auth/GOOGLE/callback으로 매핑되어있는 메소드가 있는 컨트롤러로 갑니다. 거기에서 "클래스"로 가서 이번에는 토큰 값을 가져오게 되고, 최종적으로 화면에는 json 값만 나오게 되는 구조입니다.

지금 access_token를 json로 페이지에 뿌리는 것까지는 문제없이 됩니다. 하지만... 목표는 메인 페이지에 json 값을 출력하는 게 아닌 이 json 값을 통해서 세션을 만들고 그걸 main페이지로까지 가지고 갈 수 있게끔 하는 것이 목표입니다.


하지만...이 access_token 을 이용해서 id_token을 구하고, 이 id_token을 가지고 세션에 등록하여 이용해야되는데 이 등록을 어디서 해야되는지, 어떻게 구현해야되는지 감이 안 잡힙니다ㅠㅠ

책을 사서 spring security를 이용하여 클론코딩을 해서 이해해보려고 했고, 괜히 controller나 oauth(아까 말씀드린 "클래스"입니다)를 변형시켜 세션에 등록을 해보려고 했지만 잘 안되더군요...

일주일동안 access_token을 구해보려고 노력해서 겨우 토큰을 구하는데 성공했지만, 로그인 구현 자체가 완료된 것이 아니니 이 또한 구현하려고 며칠을 보냈지만 잘 안됩니다 ㅠㅠ

어떻게 구현을 시켜야 할지 방향이나 구현 방법을 조금이라도 조언해주신다면 정말 감사드리겠습니다.


소스는

https://antdev.tistory.com/72 이 분이 사용하신 소스를 거의 대부분 참고했습니다.


이 링크는 제가 현재 작업하고 있는 링크(소스)입니다.

https://github.com/2021PassionProject/EnvironmentProject-2021/tree/main/DevelopTest/JAVATEST


개발환경은

jdk 11 

intellij 2020.3.2 버전

spring boot 2.5.3(인텔리제이에서 프로젝트 생성할 때 버전입니다)

gradle 7

thymeleaf

정도가 되겠습니다.


위에서 설명드렸던 그 컨트롤러 소스입니다.

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

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

/**
* Social Login API Server 요청에 의한 callback을 처리
* @param socialLoginType (GOOGLE, KAKAO, NAVER)
* @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(">> 소셜 로그인 API 서버로부터 받은 code::{}", code);
return oauthService.requestAccessToken(socialLoginType, code);
}
}


"클래스"라 말씀드렸던 GoogleOauth 클래스입니다. 이 클래스에서는 getOauthRecirectUrl()과 requestAccessToken() 만을 쓰고 있습니다.

@Component
@RequiredArgsConstructor
public class GoogleOauth implements SocialOauth{
@Value("${sns.google.url}")
private String GOOGLE_SNS_BASE_URL;
@Value("${sns.google.client.id}")
private String GOOGLE_SNS_CLIENT_ID;
@Value("${sns.google.callback.url}")
private String GOOGLE_SNS_CALLBACK_URL;
@Value("${sns.google.client.secret}")
private String GOOGLE_SNS_CLIENT_SECRET;
@Value("${sns.google.token.url}")
private String GOOGLE_SNS_TOKEN_BASE_URL;

HttpSession session = null;

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

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

return GOOGLE_SNS_BASE_URL + "?" + parameterString;
}

/**
* API Server로부터 받은 code를 활용하여 사용자 인증 정보 요청
*
* @param code API Server에서 받아온 code
* @return API 서버로부터 응답받은 json형태의 결과를 string으로 반영
*/
@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");
params.put("access_type","offline");

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

if(responseEntity.getStatusCode() == HttpStatus.OK) {
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");
params.put("access_type","offline");

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);
}
}
0
  • 답변 9

  • Intrager
    38
    2021-08-31 19:59:37
    @Value("${sns.google.url}")
    private String GOOGLE_SNS_BASE_URL;
    이 부븐은 따로 properties에서 관리중입니다.
  • 지붕뚫고높이차
    1k
    2021-08-31 21:51:27

    토큰을 검증했으면 인증은 된거니

    이후 서비스로직에 활용하기 위해

    인증정보를 was 새션에 저장하면 안될까요?

  • RKA
    208
    2021-08-31 22:07:19 작성 2021-08-31 23:24:00 수정됨

    토큰을 발급 받는거는 하나의 인증에 불과합니다.


    그 토큰을 통해서, 사용자의 프로필 사진이나 이름, 이메일 등등의 접근이 가능하구요.

    인증이 끝났으니, 데이터베이스에 접속 기록 추가합니다. (혹은 가입, 이때 sns를 구분할 수 있도록 무언가가 있어야겠죠)

    인증을 통해서 얻은 값으로 데이터베이스에 추가를 합니다.

    -- 추가 --

    로그인 성공시엔 사용자 정보와 함께, 세션 달아주고요.


    (이때 카카오나 구글에서 대신 인증을 해줬기 때문에, SNS에서 제공하지 않는 데이터는 추가로 사용자에게 받으시면 될 것 같아요)



  • RKA
    208
    2021-08-31 22:19:40

    몇 달이 되서 가물가물한데,

    이 부분을 저는 구현해서, Security설정에 추가해서

    oAuth를 디비 처리를 했던 기억이 있네요.


    https://github.com/spring-projects/spring-security/blob/main/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java


    도움이 되셨길..

  • 지붕뚫고높이차
    1k
    2021-08-31 22:27:48

    ROJAEKA 


    제가 질문글을 올리진 않았지만 궁금한게 있어서 적어봅니다.

    토큰을 가지고 인증을 한 이후
    관련 값을 DB 에 추가한다고 했는데

    사용자가 다른 서비스 요청시 같은 사용자임을 어떻게 알 수 있는거죠?

  • RKA
    208
    2021-08-31 22:41:01 작성 2021-08-31 22:46:53 수정됨

    지붕뚫고높이차

    다른 서비스 요청 시에 같은 사용자임을 구별하는 방법은 딱히 없었습니다.

    추가를 해야한다면, 상호 계정이 가입된 상태에서

    sns인증, 메일 인증을 하든 해서, 통합을 해야겠네요..

    (실제로 기서비스 유저의 경우, sns 계정 인증과 동시에 통합하게만 했었습니다.)


    이런걸 sso라고 하던가요?


    추가적으로 제가 학습을 했던 기억으로 댓글을 몇 자를 적는 것이여서요.

    부족한 부분에 대해서는, 날카로운 비판 감사하게 받겠습니다.





  • 지붕뚫고높이차
    1k
    2021-08-31 22:54:14

    ROJAEKA 

    아 제가 물어보고 싶었던 질문은


    1) A 가 인증토큰을 서버에 전달
    2) 서버는 인증토큰을 검증하고 관련값을 DB 에 저장 한 이후

    3) A 가 해당 서버에 다른 서비스를 요청할 때 1) 의 A 와 같은 사용자임을 어떻게 구분할 수 있는지 궁금해서요.

  • RKA
    208
    2021-08-31 23:16:38 작성 2021-09-01 01:12:59 수정됨

    지붕뚫고높이차


    죄송합니다.. 제목이 세션 질문 글이라.

    이전의 댓글을 빼놓고 적었네요.


    위에 첫 댓글에서 말씀하신 부분과 같습니다.


    토큰을 검증했으면 인증은 된거니

    이후 서비스로직에 활용하기 위해

    인증정보를 was 새션에 저장하면 안될까요?


    위에 남긴 댓글의 OAuth2AuthorizedClientService 을 구현하여,

    1. 디비에 신규 사용자를 저장을 하고, 혹은 이미 가입된 사용자라면 바뀐 정보를 처리합니다.(카카오톡 프로필 사진 업데이트)

    2. 이후 로그인 성공 핸들러를 통해서, 인증정보를 담은 세션을 추가 시켜줬습니다.


    참고하면 좋을 글

    : https://godekdls.github.io/Spring%20Security/oauth2/#oauth2authorizedclient

    : https://momentjin.tistory.com/144 ( 3번 단락에 잘 설명되어 있네요)


  • Intrager
    38
    2021-08-31 23:33:15
  • 로그인을 하시면 답변을 등록할 수 있습니다.