알고리즘

1일 1알고리즘

 

후기

4주차 결산 포스팅을 깜빡해버렸다 ㅎㅎ..

우선.. 열심히 개발했고, 어느정도 마무리가 되었다.

이번주에는 프론트 일부와 테스트를 진행할 것 같다.

이번주에 갑자기 추워지던데 대비 잘해야 할 것 같고, 다들 감기 조심하세요.. 저처럼 걸리지 말고요..

'잡담' 카테고리의 다른 글

12월 2주차 결산  (0) 2022.12.11
12월 1주차 결산  (0) 2022.12.05
11월 3주차 결산  (0) 2022.11.20
11월 2주차 결산  (0) 2022.11.14
11월 1주차 결산  (2) 2022.11.07

알고리즘

1일 1알고리즘

 

영어 회화 스터디

오랜만에 했더니 다 까먹은 기분이다.. ㅎㅎ

다시 돌아가서 단어부터 다시할 예정

 

후기

벌써 20일인거 실화인가요..?

요즘 피곤해서 그런지 잠이 많아진것 같은 느낌이 든다.

이번주도 일은 계속 진행되고 있고, 생각보다 사용중인 외부 api 데이터가 어렵게 나와 어려움이 있는것 같다.

그래서 개발속도도 늦춰지는것 같지만 열심히 해야할 것 같다.

'잡담' 카테고리의 다른 글

12월 1주차 결산  (0) 2022.12.05
11월 4주차 결산  (0) 2022.11.28
11월 2주차 결산  (0) 2022.11.14
11월 1주차 결산  (2) 2022.11.07
10월 4주차 결산  (0) 2022.10.31

알고리즘

1일 1알고리즘

 

후기

이번주에 다른 개발을 시작했다.

외부 API를 활용하는건데.. 생각보다 제대로 API가 제대로 되어있지 않아 시간이 좀 지체된 것 같다.

다음주에는 마무리할 생각이고, 다음 과제에 집중해야겠다.

'잡담' 카테고리의 다른 글

11월 4주차 결산  (0) 2022.11.28
11월 3주차 결산  (0) 2022.11.20
11월 1주차 결산  (2) 2022.11.07
10월 4주차 결산  (0) 2022.10.31
10월 3주차 결산  (0) 2022.10.24

알고리즘

1일 1알고리즘

 

후기

이번주 많이 피곤해서 그런지 퇴근하고나 주말이나 휴식을 취했다.

매번 달리는 것도 중요하지만 가끔 쉬는것도 좋을 것 같다고 생각한다.

마침 친구도 피곤하다고 해서 스터디 일정을 미뤘다.

이번에 쉰 만큼 다음주는 포스팅도 좀 하고 열심히 해야겠다.

'잡담' 카테고리의 다른 글

11월 3주차 결산  (0) 2022.11.20
11월 2주차 결산  (0) 2022.11.14
10월 4주차 결산  (0) 2022.10.31
10월 3주차 결산  (0) 2022.10.24
10월 2주차 결산  (0) 2022.10.16

우선 Spring의 의존성 주입 방법은 세 가지입니다.

  1. 필드 주입
  2. setter 주입
  3. 생성자 주입

 

1
2
3
4
5
@Service
public class ProblemService {
    @Autowired
    private ProblemRepository problemRepository;
}
cs

먼저 필드 주입입니다.

필드 바로 위에 @Autowired를 붙입니다.

 

1
2
3
4
5
6
7
8
9
@Service
public class ProblemService {
    private ProblemRepository problemRepository;
 
    @Autowired
    public void setProblemRepository(ProblemRepository problemRepository) {
        this.problemRepository = problemRepository;
    }
}
cs

다음은 setter 주입입니다.

setter 메소드 바로 위에 @Autowired를 붙입니다.

 

1
2
3
4
5
6
7
8
9
@Service
public class ProblemService {
    private final ProblemRepository problemRepository;
 
    @Autowired
    public ProblemService(ProblemRepository problemRepository) {
        this.problemRepository = problemRepository;
    }
}
cs

마지막 생성자 주입입니다.

생성자를 통해서 의존성을 주입하고, @Autowired를 붙입니다. (Spring 4.3버전 부터는 어노테이션은 생략해도 됩니다.)

1
2
3
4
5
@Service
@RequiredArgsConstructor
public class ProblemService {
    private final ProblemRepository problemRepository;
}
cs

그리고 lombok에서 제공하는 @RequiredArgsConstructor 어노테이션을 사용할 수 있습니다.

이러면 코드가 없더라도 생성자 주입을 자동으로 해줍니다.

 

저는 과거에 @Autowired를 주로 사용했었습니다.

단지 간단하고 편하다는 이유만으로 사용을 했으며, 이 방법의 문제점에 대해 생각하지 않았습니다.

 

현재, Spring에서는 "생성자 주입" 방법을 권장한다고 합니다.

필드 주입의 단점

우선 이용한 필드 주입은 @Autowired 어노테이션으로 의존성 주입을 남발할 수 있습니다.

그로 인해 생성자의 매개변수도 많아지고, 단일 책임 원칙에 위배될 수 있다고 합니다.

또한 final 사용이 불가능해서 변할 수 있는 객체 (mutable)라는 단점도 있습니다.

 

setter 주입의 단점

setter 메소드는 public으로 열려있어 객체가 변경될 가능성이 존재합니다.

실제로 변경이 있는 경우는 드물다고는 하지만 가능성이 있다는 것부터 단점입니다.

 

객체의 불변성 확보

필드 주입, setter 주입 모두 객체가 변할 수 있다고 언급하였습니다.

생성자 주입은 final 사용이 가능하고, 객체 생성 시 한 번 호출되므로 객체의 불변성을 확보할 수 있습니다.

@RequiredArgsConstructor 어노테이션은 final을 강제합니다.

객체가 final으로 생성되기 때문에 클래스 생성 시 초기값이 세팅이 되고, 변하지 않습니다. (immutable)

 

결론은 필드, setter 주입의 단점을 보완하는 생성자 주입을 쓰자였습니다.

'Spring' 카테고리의 다른 글

SpringBoot Redis 연동  (0) 2022.12.27
SpringBoot OAuth 적용 [Naver - 2]  (0) 2022.10.18
SpringBoot OAuth 적용 [Naver - 1]  (0) 2022.10.17
SpringBoot OAuth 적용 [Kakao - 2]  (0) 2022.10.17
SpringBoot OAuth 적용 [Kakao - 1]  (0) 2022.10.14

알고리즘

1일 1알고리즘

 

영어 회화 스터디

7주차 스터디를 진행했다.

단어 암기는 반복적으로 할 예정이다.

 

후기

이번주는 뭔가 많이 피곤했던것 같다.

그래서 주말은 쉬고싶어서 거의 누워만 있었던것 같은데 다음주에는 열심히 살아야할 것 같다.

벌써 11월이다. 올해 마무리도 잘해야겠다.

 

'잡담' 카테고리의 다른 글

11월 2주차 결산  (0) 2022.11.14
11월 1주차 결산  (2) 2022.11.07
10월 3주차 결산  (0) 2022.10.24
10월 2주차 결산  (0) 2022.10.16
10월 1주차 결산  (0) 2022.10.10

https://www.acmicpc.net/problem/13136

 

13136번: Do Not Touch Anything

첫 번째 줄에 좌석의 세로 크기, 가로 크기 R, C와 한 대의 CCTV가 수용할 수 있는 범위 N이 주어진다. (1 ≤ R, C, N ≤ 1,000,000)

www.acmicpc.net

문제 이름 보자마자 icpc가 떠올랐습니다.

제가 icpc 본선 갔을때도 진행자분이 항상 "Do Not Touch Anything"을 말하곤 하셨습니다 ㅋㅋ

 

진행자분을 대신하여 cctv를 설치할건데, N * N의 범위를 감시할 수 있다고 합니다.

R / N * C / N으로 구할 수 있지만 R, C을 N으로 나눈 나머지가 존재한다면 1을 추가로 더합니다.

(나머지에 해당하는 부분을 감시할 수 없기 때문입니다.)

 

100만 * 100만은 int 범위를 초과하므로 자료형은 long long을 사용합니다.

 

 

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
#include <iostream>
using namespace std;
typedef long long ll;
 
ll R, C, N;
 
void func() {
    ll x = R / N + (R % N != 0);
    ll y = C / N + (C % N != 0);
 
    cout << x * y << '\n';
}
 
void input() {
    cin >> R >> C >> N;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > Math' 카테고리의 다른 글

boj 1837 암호제작  (0) 2021.02.04
boj 15740 A+B - 9  (0) 2021.01.31
boj 1002 터렛  (0) 2021.01.27

알고리즘

1일 1알고리즘

 

영어 회화 스터디

똑같은 방식으로 진행 중이다.

생각보다 퇴근하고 공부를 안해서 주말에 몰아서 하게되고, 그러다보니 해야할게 누적되고 많은 것처럼 느껴진다.

 

후기

다른 사람들이 알고리즘을 볼때 느끼는게 이런건지.. 영어랑 친해지기가 어려운것 같다.

그래도 답이 없는 수준이라 하긴 하는데.. 그래도 아예 안하는것 보다는 낫겠지..?

개발은 그래도 템플릿을 개발해보자고 해서 이것저것 시도할 것 같다. 여기서도 배울점이 많을 것 같아서 열심히 해야겠다.

'잡담' 카테고리의 다른 글

11월 1주차 결산  (2) 2022.11.07
10월 4주차 결산  (0) 2022.10.31
10월 2주차 결산  (0) 2022.10.16
10월 1주차 결산  (0) 2022.10.10
9월 5주차 결산  (0) 2022.10.02

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

카카오에 이어 네이버 로그인입니다.

 

방법은 카카오와 비슷하며, 로직은 동일하다고 볼 수 있습니다.

 

우선 naver developers 페이지에 들어가서 네이버 계정으로 로그인합니다.

그 다음 화면 상단의 Application -> 내 애플리케이션을 누릅니다.

그러면 위 화면이 나옵니다.

여기서 "Application 등록" 버튼을 눌러서 애플리케이션을 생성합니다.

 

여기서 애플리케이션 이름과 사용할 API를 선택합니다.

이 글의 컨텐츠는 네이버 로그인이므로 네이버 로그인을 선택합니다.

네이버의 개인정보는 카카오처럼 검수과정을 거치지 않아도 이름, 이메일 등 많은 정보를 필수동의로 지정할 수 있습니다.

사용할 동의항목을 설정합니다. 저는 이메일과 별명을 선택했습니다.

 

그리고 로그인 서비스 환경을 선택합니다.

저는 PC 웹을 선택하였고, 서비스 URL과 Callback URL을 작성합니다.

서비스 URL은 말 그대로 메인 URL이며, Callback URL은 로그인 성공 시 이동할 주소를 작성합니다.

끝났다면 아래의 등록하기 버튼을 누릅니다.

 

그러면 애플리케이션이 생성되었고, 먼저 Key가 보입니다.

 

 

이제 SpringBoot로 넘어갑니다.

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

로그인 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
17
18
19
spring:
  security:
    oauth2:
      client:
        provider:
          naver:
            authorization-uri: https://nid.naver.com/oauth2.0/authorize # 네이버 로그인 요청 uri
            token-uri: https://nid.naver.com/oauth2.0/token # 토큰을 발급받기 위한 uri
            user-info-uri: https://openapi.naver.com/v1/nid/me # 유저 정보를 받아오기 위한 uri
        registration:
          naver:
            client-id: # Client ID
            client-secret: # Client Secret
            client-authentication-method: POST
            authorization-grant-type: authorization_code
            redirect-uri: # Callback URL
            scope:
              - email
              - nickname
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.naver.client-id}")
private String naverClientId;
@Value("${spring.security.oauth2.client.registration.naver.client-secret}")
private String naverClientSecret;
@Value("${spring.security.oauth2.client.registration.naver.client-authentication-method}")
private String naverAuthenticationMethod;
@Value("${spring.security.oauth2.client.registration.naver.authorization-grant-type}")
private String naverGrantType;
@Value("${spring.security.oauth2.client.registration.naver.redirect-uri}")
private String naverRedirectUri;
@Value("${spring.security.oauth2.client.provider.naver.authorization-uri}")
private String naverAuthorizationUri;
@Value("${spring.security.oauth2.client.provider.naver.token-uri}")
private String naverTokenUri;
@Value("${spring.security.oauth2.client.provider.naver.user-info-uri}")
private String naverUserInfoUri;
@Value("Bearer")
private String tokenType;
cs

 

 

코드작성 부분은 다음 글에서 다루도록 하겠습니다.

'Spring' 카테고리의 다른 글

필드 주입 vs 생성자 주입  (0) 2022.10.31
SpringBoot OAuth 적용 [Naver - 2]  (0) 2022.10.18
SpringBoot OAuth 적용 [Kakao - 2]  (0) 2022.10.17
SpringBoot OAuth 적용 [Kakao - 1]  (0) 2022.10.14
JPA order by rand() limit  (0) 2022.10.13

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 데이터가 다르기 때문에 나눴습니다.

 

신규 회원
기존 회원

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

 

 

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

알고리즘

1일 1알고리즘

 

영어 회화 스터디

저번과 동일하게 영어 단어를 외우고, 회화 진행중이다.

언제해도 영어는 어렵다.. 많은 시간이 걸릴것 같다.

 

후기

아니.. 카카오 막히니까 계정 연동되어있는 모든 서비스 못하는거 실화에요..?

블로그도 못 들어가.. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

오늘 새벽에 카톡이 풀렸고, 티스토리도 풀린지 얼마 안된것 같은데..

여전히 안되거나 렉걸리는게 많은것 같은데 빨리 개선되었으면 좋겠다.

아무튼.. 이번주 GSL 결승이 목요일 -> 토요일로 변경되어서 기회다싶었고, 티켓팅에 성공하여 보고왔다.

경기가 빨리 끝나서 아쉽긴 했지만 오랜만에 재밌게 본 것 같고, G5L의 순간을 직접 본거라서 좋았다!

'잡담' 카테고리의 다른 글

10월 4주차 결산  (0) 2022.10.31
10월 3주차 결산  (0) 2022.10.24
10월 1주차 결산  (0) 2022.10.10
9월 5주차 결산  (0) 2022.10.02
9월 4주차 결산  (0) 2022.09.26

+ Recent posts