https://emoney96.tistory.com/381

 

SpringBoot OAuth 적용 [Kakao - 1]

제가 경험했던 OAuth를 정리하기 위해 포스팅합니다. 카카오, 네이버 로그인을 시도해보았고, 차례대로 포스팅하겠습니다. 먼저 카카오입니다. 우선 kakao developers 페이지에 들어가서 회원가입/로

emoney96.tistory.com

[Kakao - 1]에서 이어지는 글입니다.

이전까지 kakao developers에서 애플리케이션 등록, SpringBoot에서 의존성 및 yml 작성까지 진행하였습니다.

이제 setting이 끝났으니 코드를 작성하겠습니다.

 

 

지금부터 나오는 API 요청들은 문서를 참고하시면 이해하기 더 수월합니다!

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

로그인 URI

1
2
3
4
5
6
7
8
9
10
// Controller
@GetMapping("/kakao/page")
public ResponseEntity<String> getKakaoLoginPage() {
    return new ResponseEntity<>(userService.getKakaoLoginPage(), HttpStatus.OK);
}
 
// Service
public String getKakaoLoginPage() {
    return kakaoAuthorizationUri + "?client_id=" + kakaoClientId + "&redirect_uri=" + kakaoRedirectUri + "&response_type=code";
}
cs

먼저 기본적인 카카오 로그인 페이지를 가져오는 api입니다.

Service에서 URI를 반환하여 보내줍니다.

 

로그인 요청

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/kakao/login")
public ResponseEntity<UserGetRes> getKakaoTokensAndSignUpOrLogin(@RequestParam String code, HttpServletResponse response) {
    // code를 이용하여 accessToken, refreshToken을 받아온다.
    HashMap<StringString> tokens = userService.getSocialTokens(code);
 
    UserInsertReq userInsertReq = userService.getSocialUserInfo(tokens);
 
    User findUser = userService.findUserByEmail(userInsertReq.getEmail());
    if (findUser == null) {
        // 유저 정보가 없으므로 회원 가입 진행
        return new ResponseEntity<>(userService.insertUser(userInsertReq, response), HttpStatus.CREATED);
    } else {
        // 유저 정보가 있으므로 토큰 갱신 후 로그인
        return new ResponseEntity<>(userService.updateUser(findUser.getEmail(), tokens, response), HttpStatus.OK);
    }
}
cs

먼저 Controller입니다.

카카오 로그인 시 나오는 코드로 accessToken, refreshToken을 발급 받습니다.

이후 카카오 api에서 유저 정보를 조회해서 가져오고,

해당 유저가 이미 가입되어 있다면 update로 토큰을 갱신하고, 신규 회원이라면 insert로 회원가입 처리를 합니다.

물론 동시에 로그인까지 완료됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public HashMap<StringString> getSocialTokens(String code) {
    String accessToken = "";
    String refreshToken = "";
 
    HashMap<StringString> keyAndValues = new HashMap<>();
 
    keyAndValues.put("tokenUri", kakaoTokenUri);
    keyAndValues.put("authenticationMethod", kakaoAuthenticationMethod);
    keyAndValues.put("grantType", kakaoGrantType);
    keyAndValues.put("clientId", kakaoClientId);
    keyAndValues.put("clientSecret", kakaoClientSecret);
    keyAndValues.put("redirectUri", kakaoRedirectUri);
 
    try {
        URL url = new URL(keyAndValues.get("tokenUri"));
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
        conn.setRequestMethod(keyAndValues.get("authenticationMethod"));
        conn.setDoOutput(true);
 
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
        StringBuilder sb = new StringBuilder();
        sb.append("grant_type=" + keyAndValues.get("grantType"));
        sb.append("&client_id=" + keyAndValues.get("clientId"));
        sb.append("&client_secret=" + keyAndValues.get("clientSecret"));
        sb.append("&redirect_uri=" + keyAndValues.get("redirectUri"));
        sb.append("&code=" + code);
 
        bw.write(sb.toString());
        bw.flush();
 
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line = "";
        String result = "";
 
        while ((line = br.readLine()) != null) {
            result += line;
        }
 
        JsonParser parser = new JsonParser();
        JsonElement element = parser.parse(result);
 
        accessToken = element.getAsJsonObject().get("access_token").getAsString();
        refreshToken = element.getAsJsonObject().get("refresh_token").getAsString();
 
        br.close();
        bw.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
 
    HashMap<StringString> tokens = new HashMap<>();
    tokens.put("accessToken", accessToken);
    tokens.put("refreshToken", refreshToken);
 
    return tokens;
}
cs

HashMap을 사용한건 카카오와 네이버 oauth를 같이 사용하기 위함이었지만 하나만 쓰신다면 HashMap을 안쓰셔도 됩니다.

 

위 로직은 Service 부분이고, code로 token을 요청해서 받는 부분으로 tokenUri에 필수 request Parameter를 추가하여 요청해야 합니다.

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token-request 관련 문서 내용입니다.

이제 받아온 내용을 Json으로 읽어서 accessToken, refreshToken을 HashMap에 저장합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public UserInsertReq getSocialUserInfo(HashMap<StringString> tokens) {
    String userInfoUri = "";
    String authenticationMethod = "";
 
    userInfoUri = kakaoUserInfoUri;
    authenticationMethod = kakaoAuthenticationMethod;
 
    JsonElement element = null;
 
    try {
        URL url = new URL(userInfoUri);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
        conn.setRequestMethod(authenticationMethod);
        conn.setDoOutput(true);
        conn.setRequestProperty("Authorization", tokenType + " " + tokens.get("accessToken"));
 
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line = "";
        String result = "";
 
        while ((line = br.readLine()) != null) {
            result += line;
        }
 
        JsonParser parser = new JsonParser();
        element = parser.parse(result);
 
        br.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
 
    return UserInsertReq.of(element, tokens);
}
cs

이제 토큰 정보를 받았으니 토큰을 이용하여 유저 정보를 요청합니다.

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info 관련 문서 내용입니다.

필수 동의 항목이나 사용자가 동의한 항목을 받아올 수 있습니다.

위와 같이 Json을 통해 가져왔습니다.

 

이거를 Dto에 넣어줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserInsertReq {
    private String email;
    private String nickname;
    private String accessToken;
    private String refreshToken;
 
    public static UserInsertReq of(JsonElement element, HashMap<StringString> tokens) {
        return UserInsertReq.builder()
                .email(element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString())
                .nickname(element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("profile").getAsJsonObject().get("nickname").getAsString())
                .accessToken(tokens.get("accessToken"))
                .refreshToken(tokens.get("refreshToken"))
                .build();
    }
}
cs

email, nickname같은 변수는 설정한 동의 항목에 따라 달라집니다.

1
2
3
4
5
public User findUserByEmail(String email) {
    Optional<User> optionalUser = userRepository.findUserByEmail(email);
    if (optionalUser.isPresent()) return optionalUser.get();
    else return null;
}
cs

회원 정보까지 가져왔다면 가져온 정보의 key로 가입된 유저인지 확인하기 위해 db에 접근합니다.

기존회원이라면 유저객체를, 신규회원이라면 null을 리턴합니다.

Repository는 JPA를 이용하였으며, 코드는 생략합니다.

1
2
3
4
5
6
7
8
9
10
11
12
public UserGetRes insertUser(UserInsertReq userInsertReq, HttpServletResponse response) {
   addCookie(response, userInsertReq.getAccessToken());
 
    return UserGetRes.of(userRepository.save(new User(userInsertReq)));
}
 
public void addCookie(HttpServletResponse response, String accessToken) {
    Cookie cookie = new Cookie("accessToken", accessToken);
    cookie.setMaxAge(3600);
    cookie.setPath("/");
    response.addCookie(cookie);
}
cs

null을 리턴하여 신규회원인 것이 확인되었으면 insert를 진행합니다.

그리고 로그인도 되니 accessToken을 쿠키에 넣어줍니다.

유효시간은 초 단위이며, 1시간으로 지정했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
public UserGetRes updateUser(String email, HashMap<StringString> tokens, HttpServletResponse response) {
    Optional<User> optionalUser = userRepository.findById(email);
    if (!optionalUser.isPresent())
        return null;
 
    User user = optionalUser.get();
    user.setAccessToken(tokens.get("accessToken"));
    user.setRefreshToken(tokens.get("refreshToken"));
    addCookie(response, user.getAccessToken());
 
    return UserGetRes.of(userRepository.save(user));
}
cs

유저 객체를 리턴하여 기존회원인 것이 확인되었으면 update를 진행합니다.

새롭게 발급받은 accessToken, refreshToken만 갱신하고, update또한 로그인도 되니 토큰을 쿠키에 넣어줍니다.

쿠키에 넣는 로직은 위 코드와 동일합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserGetRes {
    private String email;
    private String nickname;
    private String accessToken;
    private String refreshToken;
 
    public static UserGetRes of(User user) {
        return UserGetRes.builder()
                .email(user.getEmail())
                .nickname(user.getNickname())
                .accessToken(user.getAccessToken())
                .refreshToken(user.getRefreshToken())
                .build();
    }
}
cs

로그인이 되었을 때 프론트에 넘겨주는 데이터를 담은 ResponseDto입니다.

RequestDto와 같은 변수를 담고있어 Req, Res로 나눌 필요가 없긴 하지만 실제 개발에서는 Req,Res 데이터가 다르기 때문에 나눴습니다.

 

신규 회원
기존 회원

이제 로그인을 시도하면 데이터가 잘 들어가는 것을 확인할 수 있습니다.

 

 

카카오 로그인은 여기까지이며, 다음은 네이버 로그인 연동을 포스팅하겠습니다.

제가 경험했던 OAuth를 정리하기 위해 포스팅합니다.

카카오, 네이버 로그인을 시도해보았고, 차례대로 포스팅하겠습니다.

 

먼저 카카오입니다.

 

우선 kakao developers 페이지에 들어가서 회원가입/로그인을 진행합니다.

그 다음 화면 상단의 "내 애플리케이션"을 클릭하여 들어옵니다.

여기서 "애플리케이션 추가하기"를 누릅니다.

 

정보를 입력하고 저장 버튼을 누릅니다.

본인이 만든 애플리케이션이 추가가 되었으니 들어갑니다.

들어오자마자 앱 키가 뜹니다.

당연히 중요한 키들이니 노출되지 않도록 주의해야 합니다.

여기서 사용할 키는 "REST API 키" 입니다.

키 등록은 SpringBoot에서 할 예정이니 왼쪽 메뉴의 "플랫폼"으로 넘어갑니다.

 

여기서 본인의 프로젝트에 맞는 플랫폼을 등록합니다.

저는 Web을 작성하였습니다.

 

그 다음은 카카오 로그인을 활성화 합니다.

상태가 OFF인데, 눌러서 ON으로 변경합니다.

그 밑에 있는 Redirect URI는 로그인이 성공했을 때 이동할 URI를 작성해주시면 됩니다.

구분은 엔터로해서 최대 10개를 등록할 수 있습니다.

local에서 테스트하는 uri와 배포를 하셨다면 배포uri를 넣어줍니다.

그 다음 동의항목입니다.

애플리케이션에서 사용할 동의항목을 설정합니다.

하지만 카카오계정(이메일)과 같은 중요한 정보는 "필수 동의"가 안되거나 "권한 없음" 설정이 되어있는 항목이 있습니다.

이런 경우에는 "비즈니스 설정 바로가기"에 들어가서 사업자 정보를 등록해야 더 많은 권한을 받을 수 있습니다.

회사에서 진행중인 프로젝트라면 가능하겠지만 학생 또는 교육생이거나 개인 프로젝트에서는 등록하기 어렵다고 볼 수 있습니다.

 

동의항목 설정이 끝났다면 보안으로 넘어갑니다.

Client Secret 코드를 발급 받아 보안을 강화할 수 있습니다.

되도록이면 발급 받는 것을 권장합니다.

토큰 발급을 받았더라면 활성화 상태를 "사용함"으로 변경합니다.

기본값은 "사용안함"입니다.

 

 

그러면 이제 SpringBoot로 넘어갑니다.

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

우선 REST API 요청 관련은 문서를 참고해주시면 됩니다.

 

# gradle
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

# Maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

먼저 OAuth를 위한 의존성을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
  security:
    oauth2:
      client:
        provider:
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize # 카카오 로그인 요청 uri
            token-uri: https://kauth.kakao.com/oauth/token # 토큰을 발급받기 위한 uri
            user-info-uri: https://kapi.kakao.com/v2/user/me # 유저 정보를 받아오기 위한 uri
        registration:
          kakao:
            client-id: # REST API Key
            client-secret: # Client Secret Key
            client-authentication-method: POST
            authorization-grant-type: authorization_code
            redirect-uri: # Redirect URI
cs

그 다음은 yml파일에 필수 정보를 넣어줍니다.

여기 넣은 정보들은 Java 코드에서 아래와 같이 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String kakaoClientId;
@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
private String kakaoClientSecret;
@Value("${spring.security.oauth2.client.registration.kakao.client-authentication-method}")
private String kakaoAuthenticationMethod;
@Value("${spring.security.oauth2.client.registration.kakao.authorization-grant-type}")
private String kakaoGrantType;
@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String kakaoRedirectUri;
@Value("${spring.security.oauth2.client.provider.kakao.authorization-uri}")
private String kakaoAuthorizationUri;
@Value("${spring.security.oauth2.client.provider.kakao.token-uri}")
private String kakaoTokenUri;
@Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}")
private String kakaoUserInfoUri;
@Value("Bearer")
private String tokenType;
cs

 

 

글이 길어질 것 같아서 코드작성 부분은 다음 글에서 다루도록 하겠습니다.

+ Recent posts