본문 바로가기
❤️‍🔥TIL (Today I Learned)

[TIL] 2022-12-28(43day)

by elicho91 2022. 12. 28.

Project MySelectShop - OAuth2


👉 OAuth (Open Standard for Authorization)

- API 허가(Authorize)를 목적으로 JSON 형식으로 개발된 HTTP 기반의 보안 프로토콜

 - 사용자들이 사용하고자 하는 웹사이트 및 애플리케이션에 비밀번호를 제공하지 않고 접근 권한을 부여 받을 수 있게 해주는 공통적 수단으로서 사용 되어지는 기술

 

👉 User.java 수정

    private Long kakaoId;	// Kakao Id 추가
    
    // Kakao 사용자를 등록할때 Kakao Id를 넣어줘야하기 때문에 생성자 추가 
    public User(String username, Long kakaoId, String password, String email, UserRoleEnum role) {
        this.username = username;
        this.kakaoId = kakaoId;
        this.password = password;
        this.email = email;
        this.role = role;
    }

    public User kakaoIdUpdate(Long kakaoId) {
        this.kakaoId = kakaoId;
        return this;
    }

 

👉 UserRepository.java 수정

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    
    // 기존의 Kakao Id가 있는지 확인하는 메소드 추가 
    Optional<User> findByKakaoId(Long id);
    
    // 이메일 중복체크 메소드 추가
    Optional<User> findByEmail(String email);
}

 

👉 KakaoUserInfoDto.java 추가

// Access Token 사용을 해서 kakao 서버에서 사용자 정보를 담을 DTO
public class KakaoUserInfoDto {
    private Long id;
    private String email;
    private String nicknmae;

    public KakaoUserInfoDto(Long id, String nicknmae, String email) {
        this.id = id;
        this.nicknmae = nicknmae;
        this.email = email;
    }
}

 

👉 KakaoService.java 추가

public class KakaoService {
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        // 3. 필요시에 회원가입
        User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);

        // 4. JWT 토큰 반환
        String createToken =  jwtUtil.createToken(kakaoUser.getUsername(), kakaoUser.getRole());
//        response.addHeader(JwtUtil.AUTHORIZATION_HEADER, createToken);

        return createToken;
    }
}

 

👉 UserController.java 추가

	// KakaoService 의존성 주입    
    private final KakaoService kakaoService;
    
    
    @GetMapping("/kakao/callback")
    public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
        // code: 카카오 서버로부터 받은 인가 코드
        String createToken = kakaoService.kakaoLogin(code, response);

        // Cookie 생성 및 직접 브라우저에 Set
        Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, createToken.substring(7));
        cookie.setPath("/");
        response.addCookie(cookie);

        return "redirect:/api/shop";
    }

 

👉 카카오 서버에서 인가코드 받기

 

👉 인가 코드’로 ‘액세스 토큰’ 요청하기 (KakaoService.java)

// 1. "인가 코드"로 "액세스 토큰" 요청
private String getToken(String code) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", "본인의 REST API키");
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        body.add("code", code);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        return jsonNode.get("access_token").asText();
    }

 

👉 "액세스 토큰"으로 "카카오 사용자 정보" 가져오기 (KakaoService.java)

// 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        
        // 토큰을 받아서 요청하기 때문에 키값은 Authorization / 토큰에 Bearer 붙여서 보낸다
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoUserInfoRequest,
                String.class
        );

	// ObjectMapper를 통해서 파싱을 하고, 들어 있는 nickname과 email을 가지고온다.
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        Long id = jsonNode.get("id").asLong();
        String nickname = jsonNode.get("properties")
                .get("nickname").asText();
        String email = jsonNode.get("kakao_account")
                .get("email").asText();

        log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
        return new KakaoUserInfoDto(id, nickname, email);
    }

 

👉 새로운 사용자는 카카오 사용자 정보로 회원가입 하기 (KakaoService.java)

// 3. 필요시에 회원가입
// 받아온 kakao 사용자의 정보를 미리 만들 DTO를 통해 파라미터로 받기
private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {

        // Kakao Id를 활용해서 DB 에 중복된 User 가 있는지 확인
        Long kakaoId = kakaoUserInfo.getId();
        User kakaoUser = userRepository.findByKakaoId(kakaoId)
                .orElse(null);
        if (kakaoUser == null) {
            // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
            String kakaoEmail = kakaoUserInfo.getEmail();
            User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
            if (sameEmailUser != null) {
                kakaoUser = sameEmailUser;
                // 기존 회원정보에 카카오 Id 추가
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
            } else {
                // 신규 회원가입
                // random UUID를 사용하여 패스워드를 만들고 encode 함수를 사용해서 인코딩
                // User에 추가로 생성한 생성자는 이부분에서 사용
                String password = UUID.randomUUID().toString();
                String encodedPassword = passwordEncoder.encode(password);

                // email: kakao email
                String email = kakaoUserInfo.getEmail();
                
				// 새로운 사용자를 변수에 담기
                kakaoUser = new User(kakaoUserInfo.getNicknmae(), kakaoId, encodedPassword, email, UserRoleEnum.USER);
            }

            userRepository.save(kakaoUser);
        }
        return kakaoUser;
    }

 


🙋‍♂️ 소감 : 

보안면에서 안전하지만 구현이 매우 복잡하다 ㅠㅠ

OAuth 2.0  이해하려면 먼저 인증과 인가를 좀 더 공부해야겠다.

😈 아는 내용이라고 그냥 넘어가지 않기! 😈

'❤️‍🔥TIL (Today I Learned)' 카테고리의 다른 글

[TIL] 2022-12-30(45day)  (0) 2023.01.01
[TIL] 2022-12-29(44day)  (0) 2022.12.29
[TIL] 2022-12-27(42day)  (0) 2022.12.28
[TIL] 2022-12-26(41day)  (0) 2022.12.27
[TIL] 2022-12-23(40day)  (0) 2022.12.26

댓글