본문 바로가기
카테고리 없음

스프링 카카오 oauth구현

by 인제달 2023. 4. 10.

동아리원들과 진행하는 프로젝트에서 카카오 oauth를 적용하기로 했다.

Service.java
인가코드 ㅡ> 카카오 토큰 
public String getToken(String code) throws IOException{
    String host = "https://kauth.kakao.com/oauth/token";
    URL url = new URL(host);
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
    String token = "";
    try {
        urlConnection.setRequestMethod("POST");
        urlConnection.setDoOutput(true); // 데이터 기록 알려주기

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream()));
        StringBuilder sb = new StringBuilder();
        sb.append("grant_type=authorization_code");
        sb.append("&client_id={kakao developer client_id}");
        sb.append("&redirect_uri={카카오 redirect_uri}");
        sb.append("&code=" + 인가코드);

        bw.write(sb.toString());
        bw.flush();

        int responseCode = urlConnection.getResponseCode();
        System.out.println("responseCode = " + responseCode);

        BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        String line = "";
        String result = "";
        
        //json 받기
        while ((line = br.readLine()) != null) {
            result += line;
        } 
		
        //json parsing
        JSONParser parser = new JSONParser();
        JSONObject elem = (JSONObject) parser.parse(result);

        String access_token = elem.get("access_token").toString();
        String refresh_token = elem.get("refresh_token").toString();
        token = access_token;
        br.close();
        bw.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    }
    return token;
}

처음엔 프론트에서 인가코드로 요청한다고 해서 인가토큰으로 카카오에 요청해서 토큰을 받는다.

카카오에서 주는 토큰을 사용할 수도 있겠지만 우리 프로젝트에서는 jwt토큰을 따로 발급 받으려고 하기 때문에 토큰으로 다른 작업을 하지 않고 리턴받는다

카카오 토큰으로 json 데이터를 받고, 필요한 정보들을 객체로 만들어서 반환했다. 여기서는 email과 카카오 고유 id만을 추출하여 사용했다.

카카오 토큰 ㅡ> 유저정보
public ResponseEntity signUpKakao(String token, String nickname){
    String accesstoken = null;
    String refreshtoken = null;
    Member userbyEmail = null;
    try{
        //카카오토큰으로
        MemberDto user = getKakaoUser(token);
        userbyEmail = userRepository.findByEmail(user.getEmail());
        //DB에 없는 사용자라면 회원가입 처리
        if(userbyEmail == null){
            userbyEmail = Member.builder()
                    .email(user.getEmail())
                    .provider(user.getProvider())
                    .provider_id(user.getProvider_id())
                    .authority(Role.ROLE_USER)
                    .nickname(nickname)
                    .blameCount(0)
                    .blamedCount(0)
                    .build();
            userbyEmail = userRepository.save(userbyEmail);//id값을 저장하기 위해 save
            accesstoken = jwtProvider.createaccessToken(userbyEmail);//access토큰생성
            refreshtoken = jwtProvider.createRefreshToken(userbyEmail);//refresh토큰생성
            userbyEmail.setRefreshToken(refreshtoken);
            userRepository.save(userbyEmail); //refresh를 다시 db에 저장
            HttpHeaders httpHeaders = new HttpHeaders();
            //회원가입 이후 헤더에 토큰 발급
            httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + accesstoken);
            httpHeaders.add("refresh_token",refreshtoken);
            ResponseMemberDto responseMemberDto = new ResponseMemberDto(
            								userbyEmail.getEmail(),userbyEmail.getNickname());
            return ResponseEntity.ok().headers(httpHeaders).body(responseMemberDto);
        }
    }catch (IOException e){
        e.printStackTrace();
    }

    return new ResponseEntity<>("이미 회원가입되어있는 유저입니다.", HttpStatus.OK);

}

다른 글들을 참고했을 때 대부분 데이터의 중복여부를 통해서 회원가입과 로그인을 한번에 처리할 수 있도록 했는데 여기서는 익명성을 보장하기 위해 개인의 닉네임을 설정할 수 있도록 회원가입과 로그인을 분리했다.

Controller
카카오페이지 로그인
@Controller
@RequestMapping("/api")
public class KaKaoController {
    @Autowired
    KakaoService ks;

    @GetMapping("/member/do")
    @ApiOperation(value = "카카오로그인", notes = "이것도 테스트용으로 카카오로그인할 수 있게 구현")
    public String loginPage(){
        return "kakaoCI/login";
    }

}
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
</head>
<body>
<a class="p-2" href="https://kauth.kakao.com/oauth/authorize?client_id=55ec14b78e17e978a4a3b64971060784&redirect_uri=http://43.201.174.163:8080/api/kakao/access_token&response_type=code">
    회원가입
</a>
</body>
</html>

카카오페이지를 불러와서 처리할 수 있도록 만들었다. 테스트할 수 있도록 회원가입을 누르면 토큰을 받을 수 있도록 만들었다.

@GetMapping("/kakao/access_token")
@ApiOperation(value = "카카오 토큰받기",notes = "이건 직접호출할 필요없이 member/do로 버튼누르면 실행됨\n" +
        " 이토큰을 받고 /api/kakao/testsign <-- 회원가입\n" +
        "/api/kakao/testlogin <== 로그인으로 사용하면됨")
public String testLogin(@RequestParam String code, Model model) throws IOException {
    return ks.getToken(code);
}

@PostMapping("/kakao/testsign")
@ApiOperation(value = "카카오 토큰으로 회원가입")
public ResponseEntity signUpKakao(@RequestParam String accessToken){
    return ks.signUpKakao(accessToken,"테스트용");
}

@PostMapping("/kakao/testlogin")
@ApiOperation(value = "카카오 토큰으로 로그인")
public ResponseEntity loginKakao(@RequestParam String accessToken) throws IOException{
    return ks.loginKakao(accessToken);
}

버튼으로 받은 인가코드를 통해 토큰발급까지 처리한다. 이렇게 받은 토큰으로 로그인을 하거나 회원가입을 해서 db에 test data를 넣는 용도로 사용할 수 있게 했다.

 

("api/member/do")로그인시 결과창

회원가입을 누르면 카카오페이지가 나올텐데 로그인을 하면 위처럼 인가코드를 받고 다시 토큰으로 받는 과정을 거쳐 토큰이 발급된다.

 

발급받은 카카오 토큰으로 로그인 및 회원가입을 진행하면 된다.


내가봐도 먼가 어설픈 코드

스스로가 봐도 먼가 어색한 코드인 것 같다. oauth2 인터페이스가 따로 있다고 해서 다음엔 다른 oauth도 추가하면서 인터페이스를 활용해보려고 한다. security도 따로 있다고 하니까 꼭 한번은 써봐야 할 것 같다. jwt를 어느정도 한 사람이라면 회원가입과 로그인에서 비효율적이라는 것을 봤을 것 같은데 아직 캐싱을 제대로 몰라서 활용하지 못했다. redis부터 빨리 공부해서 비용을 줄이는 작업부터 해야할 것 같다.


여러가지 글 들을 많이 참조해서 만들었지만 다음 글을 기반으로 살을 붙이고 공부하는데 이용했다.

https://velog.io/@dktlsk6/Spring%EC%9C%BC%EB%A1%9C-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0