25일차 - JWT 토큰

이해찬·2023년 9월 7일
1

항해일지

목록 보기
23/35

JWT 토큰을 활용

1. 회원 가입
2. 로그인
3. 게시글 작성
4. 게시글 전체 조회
5. 게시글 선택 조회
6. 선택한 게시글 수정
7. 선택한 게시글 삭제


ERD 설계

1. 회원 가입

입려한 유저 정보(username,password)를 받았을 때, 정규식 표현에 맞는 요구조건을 확인하고 DB에 중복된 username이 없다면 회원을 저장하고 client로 메시지, 상태코드 반환.

입력한 정보 -> dto 객체에서 받고 -> 정규식이 맞는지 검증 ->jpa와 연동되어있는 entity 객체의 user의 db와 중복 검사를 한다. -> 회원 저장 -> 바디로 메시지,상태코드 출력


  • http요청 ->signupRequestDto 객체에 담아서 정규식 표현 + @vaild로 검증
  • 유저컨트롤러 -> 유저서비스에서 비밀번호를 암호화 해야된다.-> 오류

📟오류 1)

💻 해결 방법
BCryptPasswordEncoder 반환하는 passwordEncoder 클래스를 빈으로 등록해야 한다.
BCryptPasswordEncoder는 스프링 시큐리티에서 제공하는 클래스로

  • @Configuration @Bean
  • bcrypt는 해시 함수 중 하나로 비밀번호를 암호화 시킨다.
  • 자동으로 '솔트'를 생성,관리 = 솔트는 해시를 생성할 때 추가되는 랜덤한 값으로, 같은 패스워드라도 솔트값이 다르면 해시된 결과가 다르다.

2. 로그인

db에서 username을 사용하여 유무를 확인한 뒤, password와 비교, 로그인 성공시 성공한 유저의 정보를 "JWT"를 활용하여 토큰을 발급, 발급한 토큰을 Header에 추가하고 성공했다는 메시지, 상태코드와 함께 client에 반환.

레포지토리에서 db 확인 후 -> password 일치하는지 -> 성공시 jwt 발급 -> 바디로, 메시지,상태코드 출력


  • Jwt유틸 클래스 생성 -> dto -> controller -> service
  • service 에서 로그인 성공 시, -> 토큰 발급 , header에 추가, 반환

🤷‍♂️HttpServletResponse

🙆‍♂️ 개념 설명

HttpServletRequest: 이 객체는 클라이언트가 보낸 HTTP 요청에 대한 정보를 포함
이 객체를 통해 요청 메소드(GET, POST 등), 헤더, 파라미터 등의 정보를 알 수 있다.
HttpServletResponse: 이 객체는 서버가 클라이언트에게 보낼 HTTP 응답을 구성하는 데 사용
상태 코드(200, 404 등), 헤더, 바디 등의 정보를 설정할 수 있다.
=>res를 활용해서 헤더에 전송


3. 게시글 작성

가져온 토큰을 검사하여 유효한 경우, 게시글 작성, 수정, 삭제가 가능

jwtutil 클래스를 활용해서 토큰을 만든 뒤 검증 -> 게시글 작성,수정,삭제

📟오류2)

Header에 추가하고 성공 -> 쿠키에 저장해서 오류?


public void addJwtToCookie(String token, HttpServletResponse res) {
        try {
            token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
            cookie.setPath("/");

            // Response 객체에 Cookie 추가
            res.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

해당 코드는 쿠키에 추가하고 있기 때문에, 밑에 헤더에 추가하는 걸로 바꿔야 한다.

public void addJwtToCookie(String token, HttpServletResponse res) {
        try {
            token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
            cookie.setPath("/");

           
            res.setHeader(AUTHORIZATION_HEADER, token);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

💻 해결 방법

  • 쿠키에 추가하면 포스트맨에서 요청할 때, 굳이 value값을 넣지 않아도 자동으로 넣어준다.
  • header에 직접적으로 사용자가 넣으려면 setHeader를 사용해야 한다.
@PostMapping("/post")
    public ResponseDto createPost(@RequestBody RequestDto requestDto, HttpServletRequest req) {
       >> req.getHeader(JwtUtil.AUTHORIZATION_HEADER); <<
        return postService.createPost(requestDto, req);
    }

포스트컨트롤러에도 req 요청 부분에 직접 getHeader를 담아야한다.


//게시글 작성
@Transactional
    public ResponseDto createPost(RequestDto requestDto, HttpServletRequest req) {

        String username = validToken(req);
        User user = userRepository.findByUsername(username).orElseThrow(
                () -> new IllegalArgumentException("유효하지 않은 사용자입니다.")
        );

        Post post = new Post(requestDto);
        post.setUser(user);
        user.getPosts().add(post);


        Post savePost = postRepository.save(post);

        ResponseDto result = new ResponseDto(savePost,username);

        return result;
    }
    
    //토큰 검사
    public String validToken(HttpServletRequest request) {
       >> String token = jwtUtil.getTokenFromRequest(request); <<
        token = jwtUtil.substringToken(token);
        if(!jwtUtil.validateToken(token)){
            throw new IllegalArgumentException("유효하지 않는 토큰입니다.");
        };

        Claims claims = jwtUtil.getUserInfoFromToken(token);
        String username = claims.getSubject();

        return username;
    }

토큰 검사 부분에서 getTokenFromRequest 메서드가 헤더를 가지고 와야한다.

// HttpServletRequest 에서 Cookie Value : JWT 가져오기
    public String getTokenFromRequest(HttpServletRequest req) {
        return req.getHeader(JwtUtil.AUTHORIZATION_HEADER);
        
        //밑에 부분은 쿠키에 넣으려고 할 때
//        Cookie[] cookies = req.getCookies();
//        if(cookies != null) {
//            for (Cookie cookie : cookies) {
//                if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
//                    try {
//                        return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
//                    } catch (UnsupportedEncodingException e) {
//                        return null;
//                    }
//                }
//            }
//        }
//        return null;
    }
    

jwt cookie 에 생성 -> header에 직접 담아서 반환

public void addJwtToCookie(String token, HttpServletResponse res) {
        try {
            token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
            cookie.setPath("/");

            // Response 객체에 Cookie 추가
            res.addCookie(cookie);
            >> res.setHeader(AUTHORIZATION_HEADER, token); << header 부분
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

발급한 토큰을 굳이 헤더에 직접적으로 넣으면서 구현하는 것 보다는 역시 쿠키에 넣어서 쓰는게 더 편한 것 같다.

헤더를 사용하여 토큰 반환:
클라이언트는 이 헤더 값을 읽어 토큰을 저장하고, 이후 요청에서 이 토큰을 헤더에 포함시켜야 합니다. Postman과 같은 테스트 도구를 사용할 때, 매번 토큰 값을 수동으로 입력해야 합니다. 웹 애플리케이션에서는 자바스크립트를 사용하여 응답 헤더에서 토큰을 추출하고, 이후 요청에서 헤더에 포함시킬 수 있습니다.

쿠키를 사용하여 토큰 반환:
이 방식에서는 토큰이 HTTP 응답의 쿠키에 포함되어 반환됩니다. 웹 브라우저는 자동으로 이 쿠키를 저장하고, 동일한 도메인으로의 이후 요청에서 쿠키를 자동으로 포함시킵니다. Postman과 같은 도구를 사용할 때, 한 번 쿠키를 설정하면 동일한 세션에서는 수동으로 토큰을 추가할 필요가 없습니다. 쿠키는 도메인과 경로에 대한 제한이 있어, 다른 도메인에서는 사용할 수 없습니다. 또한, 쿠키의 사용은 크로스 사이트 요청 위조(CSRF)와 같은 공격에 노출될 수 있기 때문에 추가적인 보안 조치가 필요합니다.

결론:
두 방식 모두 장단점이 있습니다. 헤더를 사용하는 방식은 수동으로 토큰 값을 관리해야 하지만, 보안 문제에 대한 리스크가 상대적으로 낮습니다. 반면, 쿠키를 사용하는 방식은 사용이 편리하지만, CSRF와 같은 공격에 노출될 수 있는 리스크가 있습니다. 따라서 선택한 방식에 따라 적절한 보안 조치를 취해야 합니다.


profile
디자인에서 개발자로

0개의 댓글