Backend/spring

[error] getOutputStream() has already been called for this response

dddzr 2025. 2. 23. 19:50

๐Ÿšจ Could not write JSON: getOutputStream() has already been called for this response

 

๐Ÿ”ฅ ์˜ค๋ฅ˜ ์›์ธ

HttpServletResponse์˜ getOutputStream() ๋˜๋Š” getWriter()๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ!
์ฆ‰, ์‘๋‹ต์„ ํ•œ ๋ฒˆ ๋ณด๋‚ธ ํ›„์— ๋˜ ์‘๋‹ต์„ ๋ณด๋‚ด๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. 

 

๐Ÿ“– ์—๋Ÿฌ ์ฝ”๋“œ (์ˆ˜์ • ์ „)

 โžก๏ธ ์ฟ ํ‚ค๋ฅผ response.addCookie()๋กœ ์„ค์ •ํ•œ ํ›„, ResponseEntity๋กœ JSON ์‘๋‹ต์„ ๋ฐ˜ํ™˜.
 โžก๏ธ ํ•˜์ง€๋งŒ ResponseEntity๊ฐ€ HTTP ์‘๋‹ต์„ ์„ค์ •ํ•˜๋Š” ๊ณผ์ •์—์„œ response.getOutputStream()์„ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถฉ๋Œ์ด ๋ฐœ์ƒ.

   @PostMapping("/login")
   public ResponseEntity<?> login(@RequestBody AuthRequest authRequest, HttpServletResponse response) {
       try {
           Authentication authentication = authenticationManager.authenticate(
                   new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
           );
           UserDetails userDetails = (UserDetails) authentication.getPrincipal();
           String accessToken = jwtUtil.generateAccessToken(userDetails);
           String refreshToken = jwtUtil.generateRefreshToken(userDetails);          

        // โŒ HttpOnly ์ฟ ํ‚ค๋กœ refreshToken ์„ค์ •
           Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
           refreshTokenCookie.setHttpOnly(true);
           refreshTokenCookie.setSecure(true);
           refreshTokenCookie.setPath("/");
           refreshTokenCookie.setMaxAge(7 * 24 * 60 * 60);
           response.addCookie(refreshTokenCookie);
           Map<String, Object> responseBody = new HashMap<>();

           responseBody.put("success", true);
           responseBody.put("message", "๋กœ๊ทธ์ธ ์„ฑ๊ณต!");
           responseBody.put("tokens", Map.of("accessToken", accessToken)); // "refreshToken", refreshToken

           return ResponseEntity.ok(response);

       } catch (AuthenticationException e) { // ๋กœ๊ทธ์ธ ์‹คํŒจ
           return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                   .body(Map.of("success", false, "message", "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

       }
   }

 

 

๐Ÿ›  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿ“–  ์ˆ˜์ • ์ฝ”๋“œ

 โžก๏ธ  ResponseEntity์—์„œ Set-Cookie ํ—ค๋” ์ง์ ‘ ์„ค์ •

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody AuthRequest authRequest) {
    try {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
        );
        
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        String accessToken = jwtUtil.generateAccessToken(userDetails);
        String refreshToken = jwtUtil.generateRefreshToken(userDetails);

        // โœ… Set-Cookie ํ—ค๋” ์ถ”๊ฐ€
        String cookieHeader = "refreshToken=" + refreshToken + "; Path=/; HttpOnly; Secure; Max-Age=" + (7 * 24 * 60 * 60);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Set-Cookie", cookieHeader);

        return ResponseEntity.ok()
                .headers(headers)
                .body(Map.of(
                    "success", true,
                    "message", "๋กœ๊ทธ์ธ ์„ฑ๊ณต!",
                    "accessToken", accessToken
                ));

    } catch (AuthenticationException e) { // ๋กœ๊ทธ์ธ ์‹คํŒจ
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(Map.of("success", false, "message", "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

    }
}