https://emoney96.tistory.com/384

 

SpringBoot OAuth 적용 [Naver - 1]

카카오에 이어 네이버 로그인입니다. 방법은 카카오와 비슷하며, 로직은 동일하다고 볼 수 있습니다. 우선 naver developers 페이지에 들어가서 네이버 계정으로 로그인합니다. 그 다음 화면 상단의

emoney96.tistory.com

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

Naver developers에서 애플리케이션을 등록하고, SpringBoot에서 의존성 및 yml 작성까지 완료했습니다.

이제 코드작성을 하겠습니다.

코드는 카카오 OAuth와 거의 동일하다고 볼 수 있습니다.

 

우선 개발 가이드 및 API 명세를 참고하시는게 많은 도움이됩니다.

https://developers.naver.com/docs/login/devguide/devguide.md

 

네이버 로그인 개발가이드 - LOGIN

네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디

developers.naver.com

https://developers.naver.com/docs/login/api/api.md

 

네이버 로그인 API 명세 - LOGIN

네이버 로그인 API 명세 네이버 로그인 API는 네이버 로그인 인증 요청 API, 접근 토큰 발급/갱신/삭제 요청API로 구성되어 있습니다. 네이버 로그인 인증 요청 API는 여러분의 웹 또는 앱에 네이버

developers.naver.com

 

로그인 URI

1
2
3
4
5
6
7
8
9
10
11
// Controller
@GetMapping("/naver/page")
public ResponseEntity<String> getNaverLoginPage() {
    return new ResponseEntity<>(userService.getNaverLoginPage(), HttpStatus.OK);
}
 
// Service
public String getNaverLoginPage() {
    String state = new BigInteger(130new SecureRandom()).toString();
    return naverAuthorizationUri + "?client_id=" + naverClientId + "&redirect_uri=" + naverRedirectUri + "&response_type=code&state=" + state;
}
cs

먼저 네이버 로그인 페이지를 가져오는 api입니다.

Service에서 URI를 반환하며, state라는 난수를 추가합니다.

 

로그인 요청

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/naver/login")
public ResponseEntity<UserGetRes> getNaverTokensAndSignUpOrLogin(@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에 회원 정보를 요청하고, 해당 유저가 신규유저인지, 기존유저인지 확인합니다.

신규유저라면 회원가입을, 기존유저라면 정보 갱신 후 로그인을 진행합니다.

 

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
58
public HashMap<StringString> getSocialTokens(String code) {
    String accessToken = "";
    String refreshToken = "";
 
    HashMap<StringString> keyAndValues = new HashMap<>();
 
    keyAndValues.put("tokenUri", naverTokenUri);
    keyAndValues.put("authenticationMethod", naverAuthenticationMethod);
    keyAndValues.put("grantType", naverGrantType);
    keyAndValues.put("clientId", naverClientId);
    keyAndValues.put("clientSecret", naverClientSecret);
    keyAndValues.put("redirectUri", naverRedirectUri);
    keyAndValues.put("state"new BigInteger(130new SecureRandom()).toString());
 
    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);
        sb.append("&state=" + keyAndValues.get("state"));
        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

카카오, 네이버 oauth를 함께 사용하고 있고, 로직이 동일하기에 한 메소드에서 처리하려고 했고, HashMap을 사용하였습니다.

하나만 쓰신다면 HashMap을 사용하실 필요는 없습니다.

 

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

https://developers.naver.com/docs/login/devguide/devguide.md#3-4-4-%EC%A0%91%EA%B7%BC-%ED%86%A0%ED%81%B0-%EB%B0%9C%EA%B8%89-%EC%9A%94%EC%B2%AD 관련 문서입니다.

그 다음 발급받은 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 = naverUserInfoUri;
    authenticationMethod = naverAuthenticationMethod;
 
    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.naver.com/docs/login/devguide/devguide.md#3-4-5-%EC%A0%91%EA%B7%BC-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%ED%94%84%EB%A1%9C%ED%95%84-api-%ED%98%B8%EC%B6%9C%ED%95%98%EA%B8%B0 관련 문서입니다.

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

 

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("response").getAsJsonObject().get("email").getAsString())
                .nickname(element.getAsJsonObject().get("response").getAsJsonObject().get("nickname").getAsString())
                .accessToken(tokens.get("accessToken"))
                .refreshToken(tokens.get("refreshToken"))
                .build();
    }
}
cs

이제 받아온 정보를 Dto에 넣어줍니다.

 

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

그 다음은 email로 가입된 유저인지 확인합니다.

기존유저라면 유저객체를, 신규회원이라면 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만 갱신하고 로그인 처리를 위해 accessToken을 쿠키에 넣어줍니다.

 

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와 같은 변수를 담고있어서 중복되는 문제가 있지만 비밀번호처럼 보안이 필요한 변수가 있으면 다르게 나오지 않을까 생각합니다.

 

신규 회원
기존 회원

분명 신규회원, 기존회원이라는 차이가 있지만 accessToken, refreshToken은 동일합니다.

네이버에서 토큰 발급은 유효기간이 끝날때까지 동일한 토큰을 발급하는 것 같습니다.

따라서 이 방법보다는 refreshToken으로 accessToken을 재발급 받는 것이 좋지 않을까 생각은 듭니다.

https://developers.naver.com/docs/login/devguide/devguide.md#5-1-2-%EA%B0%B1%EC%8B%A0-%ED%86%A0%ED%81%B0%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC 토큰 갱신 관련 링크입니다.

'Spring' 카테고리의 다른 글

SpringBoot Redis 연동  (0) 2022.12.27
필드 주입 vs 생성자 주입  (0) 2022.10.31
SpringBoot OAuth 적용 [Naver - 1]  (0) 2022.10.17
SpringBoot OAuth 적용 [Kakao - 2]  (0) 2022.10.17
SpringBoot OAuth 적용 [Kakao - 1]  (0) 2022.10.14

+ Recent posts