๐Ÿ”ฅ TIL - Day 46

dhk22ยท2021๋…„ 11์›” 3์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
55/93

์บ ํ”„๊ธฐ๊ฐ„ ๋™์•ˆ ์†Œ์…œ ๋กœ๊ทธ์ธ๋งŒ ๋ช‡ ๋ฒˆ์งธ ๊ตฌํ˜„ํ•˜๋Š” ๊ฑด์ง€..
ํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์ด ์žˆ์–ด์„œ ๋ญ๊ฐ€ Best practice์ธ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค.
์ผ๋‹จ ์ค‘์š”ํ•œ๊ฑด ์กฐ๊ธˆ ๋ถ€์กฑํ•˜๋”๋ผ๋„ ๊ตฌํ˜„ํ•  ์ค„ ์•„๋Š” ๊ฒƒ !

์ด๋ฒˆ์—” Springboot์—์„œ ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ํ•ด๋ณด์ž.


์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ๋ก (์ƒ_๋žต)

  • ์ฝœ๋ฐฑ์ฃผ์†Œ: localhost:8080/user/kakao/callback

๐Ÿ“Œ ์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ํ–ˆ์„ ๋•Œ ์ฒ˜๋ฆฌ

ํ”„๋ก ํŠธ ์ชฝ์—์„œ ๋กœ๊ทธ์ธ ์š”์ฒญ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ ธ์„ ๋•Œ Rest API ํ‚ค์™€ ์ฝœ๋ฐฑ์ฃผ์†Œ๋ฅผ ํฌํ•จํ•œ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ํ•œ๋‹ค.

GET /oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code HTTP/1.1
Host: kauth.kakao.com

์นด์นด์˜ค์—์„œ ์š”์ฒญํ•˜๋ผ๋Š” ๋Œ€๋กœ ๊ทธ๋Œ€๋กœ ๋ณด๋‚ด์ค€๋‹ค.

<button id="login-kakao-btn" 
    onclick="location.href='https://kauth.kakao.com/oauth/authorize?client_id=[REST_APIํ‚ค]&redirect_uri=http://localhost:8080/user/kakao/callback&response_type=code'">

์‚ฌ์šฉ์ž๋Š” ์ด๋™๋œ ์นด์นด์˜ค ์ธ์ฆ ํŽ˜์ด์ง€์—์„œ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ด๋‹ค.



๐Ÿ“Œ ์ธ์ฆ์„ฑ๊ณต ์‹œ ์ฝœ๋ฐฑ์ฃผ์†Œ๋กœ ์˜ค๋Š” ์ธ๊ฐ€์ฝ”๋“œ ์ฒ˜๋ฆฌ

์ธ์ฆ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์ง€์ •ํ•œ Redirect URI๋กœ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ํฌํ•จํ•ด์„œ ๋Œ์•„์˜ฌ ๊ฒƒ์ด๋‹ค.

์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ๋ฐ›์•„์„œ ์—‘์„ธ์Šค ํ† ํฐ์„ ์š”์ฒญํ•ด์•ผ ํ•˜๋ฏ€๋กœ Redirect URI๋กœ ์˜ค๋Š” ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

    @GetMapping("/user/kakao/callback")
    public String kakaoLogin(String code) {
        userService.kakaoLogin(code);

        return "redirect:/";
    }


๐Ÿ“Œ ์ธ๊ฐ€์ฝ”๋“œ๋กœ ์นด์นด์˜ค์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ์—‘์„ธ์Šค ํ† ํฐ ์š”์ฒญ

์œ„ URL๋กœ Required๊ฐ€ O์ธ (ํ•„์ˆ˜) ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒ ๊ตฌ์„ฑํ•ด์„œ ๋ณด๋‚ด์ฃผ๋ฉด ๋œ๋‹ค.

    public void kakaoLogin(String authorizedCode) {
        String accessToken = getAccessToken(authorizedCode);
    }

HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ํฌ๊ฒŒ ํ—ค๋”์™€ ๋ฐ”๋””๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค.

  • ํ—ค๋”: HttpHeaders
  • ๋ฐ”๋””: MultiValueMap

ํ—ค๋”์™€ ๋ฐ”๋””๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค๋ฉด HttpEntity๋กœ ํ—ค๋”์™€ ๋ฐ”๋””๋ฅผ ๋ฌถ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
HttpEntity์˜ ์ œ๋„ค๋ฆญ์€ ๋ฐ”๋””์˜ ํƒ€์ž…์ด๋‹ค.

Http ์š”์ฒญ์€ RestTemplate์„ ์‚ฌ์šฉํ•œ๋‹ค.

  private String getAccessToken(String authorizedCode) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
        paramMap.add("grant_type", "authorization_code");
        paramMap.add("client_id", "๋‚ด๊บผAPI_KEY");
        paramMap.add("redirect_uri", "http://localhost:8080/user/kakao/callback");
        paramMap.add("code", authorizedCode);
        
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(paramMap, headers);

        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                requestEntity,
                String.class
        );

        String body = response.getBody();
        JSONObject tokenJson = new JSONObject(body);
        
        return tokenJson.getString("access_token");
    }

์‘๋‹ต์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์˜จ๋‹ค.
ํ•„์š”ํ•œ๊ฑด ์—‘์„ธ์Šค ํ† ํฐ์ด๋‹ˆ๊นŒ access_token ๋ถ€๋ถ„๋งŒ ํŒŒ์‹ฑํ•œ๋‹ค.



๐Ÿ“Œ ์—‘์„ธ์Šค ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ


์š”์ฒญ์€ ์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•œ๋‹ค.

๋™์ผํ•˜๊ฒŒ ํ—ค๋”์™€ ๋ฐ”๋””๋ฅผ ๊ตฌ์„ฑํ•ด์„œ HttpEntity๋กœ ๋ฌถ๊ณ  RestTemplate์œผ๋กœ ์š”์ฒญํ•˜๋ฉด ๋œ๋‹ค.
(ํ•„์ˆ˜ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์—†์œผ๋ฏ€๋กœ ๋ฐ”๋””๋Š” ๋น„์›Œ๋‘”๋‹ค.)

    private KakaoUserInfo getKakaoUserInfo(String accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(headers);

        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoProfileRequest,
                String.class
        );

        JSONObject body = new JSONObject(response.getBody());
        Long id = body.getLong("id");
        String email = body.getJSONObject("kakao_account").getString("email");
        String nickname = body.getJSONObject("properties").getString("nickname");

        return new KakaoUserInfo(id, email, nickname);
    }

์‘๋‹ต์ƒ˜ํ”Œ

ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ž˜ ๋ฝ‘์•„์„œ User DB๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด ๋œ๋‹ค.



๐Ÿ“Œ ์‚ฌ์šฉ์ž ์ •๋ณด๋กœ ํšŒ์›๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ

ํšŒ์›๊ฐ€์ž… ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ user DB๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ์‹์€ ๊ทธ๋•Œ ๊ทธ๋•Œ ๋งž์ถฐ์„œ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

์ค‘์š”ํ•˜ ๊ฑด ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ์ด๋‹ค.
Spring Security๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Spring Security์˜ ๋ฃฐ์„ ๋”ฐ๋ผ์•ผ ํ•œ๋‹ค.
์ง€๊ธˆ ์นด์นด์˜ค๋ฅผ ํ†ตํ•ด ์ธ์ฆ์€ ๋์ง€๋งŒ Spring Security๋Š” ์ด ์‚ฌ์‹ค์„ ๋ชจ๋ฅธ๋‹ค.
์•Œ๋ ค์ค˜์•ผ ์ด ์ธ์ฆ์ด ์„ธ์…˜๊ธฐ๋ฐ˜์œผ๋กœ ์ญ‰ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

์ž„์˜๋กœ ์ธ์ฆ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” AuthenticationManager๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
AuthenticationManager๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด ์ผ๋‹จ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

์ด์ œ ์ž„์˜๋Œ€๋กœ ์–ด๋–ค ํ•„ํ„ฐ๋ฅผ ์ง€๋‚˜ ํ† ํฐ์„ ๋ฐœํ–‰๋ฐ›์€ ๊ฑธ๋กœ ๊ฐ€์ •ํ•˜๊ณ  ํ† ํฐ์„ ์ƒ์„ฑํ•œ๋‹ค.
์ „๋žต์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ ์ด๋ฒˆ ์‹ค์Šต์—์„œ๋Š” username๊ณผ password๋ฅผ ์ž„์˜๋กœ ์ƒ์„ฑํ–ˆ์œผ๋ฏ€๋กœ UsernamePasswordAuthenticationToken ์„ ํ†ตํ•ด ํ† ํฐ์„ ์ƒ์„ฑํ•œ๋‹ค.

์ด ํ† ํฐ์ด ์ผ์ข…์˜ ํ†ตํ–‰์ฆ์ด ๋˜๋Š”๊ฑด๋ฐ UsernamePasswordAuthenticationToken์„ ํ†ตํ•ด ์ƒ์„ฑ๋œ ํ† ํฐ์€ ์•„์ง ์ธ์ฆ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ์˜๋ฏธ๋กœ isAuthenticated ํ•„๋“œ๊ฐ€ false ์ด๋‹ค.

AuthenticationManager๋ฅผ ์ด์šฉํ•ด์„œ ์ž„์˜๋กœ ์ธ์ฆ๋œ ํ† ํฐ์ด๋ผ๊ณ  ์ฒดํฌํ•œ๋‹ค.

Authentication authentication = authenticationManager.authenticate(kakaoUsernamePassword);

์ด์ œ ํ† ํฐ์ด ์ธ์ฆ๋˜์—ˆ์œผ๋ฏ€๋กœ authentication ์— ์„ธํŒ…ํ•œ๋‹ค.

SecurityContextHolder.getContext().setAuthentication(authentication);

SecurityContextHolder > SecurityContext > Authentication

์•„์ง ์ž์„ธํžˆ๋Š” ํŒŒ์•…๋˜์ง€ ์•Š์ง€๋งŒ SecurityContext์˜ Authentication์— ํ† ํฐ์„ ์„ธํŒ…ํ•˜๋ฉด ์ดํ›„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @AuthenticationPrincipal๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

์ด๊ฒƒ๋งŒ ์•Œ์•„๋„ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์„๊นŒ...? ใ…Ž

profile
์ข€ ๋” ์ฒœ์ฒœํžˆ ๊นŒ๋จน๊ธฐ ์œ„ํ•ด ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๐Ÿง

0๊ฐœ์˜ ๋Œ“๊ธ€