관리 메뉴

bright jazz music

blog33 : 쿠키를 통한 인증 및 검증 본문

Projects/blog

blog33 : 쿠키를 통한 인증 및 검증

bright jazz music 2023. 2. 16. 21:37

지난 번 포스팅에서는 회원이 로그인을 하면 토큰이 발급되고, 인증이 필요한 페이지로 이동하려 할 때 DB에서 해당 토큰을 검증하고, 이동한 후에는 해당 회원의 primary id를 출력해 주는 과정을 다루었다.

//AuthController.java
    @PostMapping("/auth/login")
    public SessionResponse login(@RequestBody Login login){

        String accessToken = authService.signin(login);
        return new SessionResponse(accessToken);
    }
### auth.http

POST http://localhost:8080/auth/login
Content-Type: application/json

{
  "email" : "catnails@gmail.com",
  "password" : "1234"
}

 

---------

 

이번 포스팅에서는 쿠키를 통해서 인증 및 검증하는 내용을 다룬다. 저번 포스팅처럼 헤더에 토큰을 넣어주는 방식도 사용하기는 하지만 쿠키를 사용한 인증 방식을 훨씬 더 일반적으로 사용된다.

 

위에서 보듯이 현재는 json으로 응답을 반환받고 있다. 그러나 이제부터는 쿠키를 사용하여 인증을 유지해야 하기 때문에 header에 쿠키 값을 넣은 응답을 받아야 한다.

 

1. AuthController 수정

//AuthController.java

package com.endofma.blog.controller;


import com.endofma.blog.request.Login;
import com.endofma.blog.response.SessionResponse;
import com.endofma.blog.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;


@Slf4j
@RestController
@RequiredArgsConstructor
public class AuthController {

    //생성자를 통한 주입
    //private final UserRepository userRepository;
    //로그인 및 인증 절차를 서비스에 위임
    private final AuthService authService;

//AuthController.java
    @PostMapping("/auth/login")
    public ResponseEntity<Object> login(@RequestBody Login login){
//    public SessionResponse login(@RequestBody Login login){

        String accessToken = authService.signin(login);
//        return new SessionResponse(accessToken);
        //json이 아닌 헤더에 쿠키를 담은 값을 반환받아야 한다.
        //스프링 5.0이전에는 HttpServletResponse 에 넣어주는 것이 예전 방식이었다. 여기서는 ResponseCookie를 사용한다.
        ResponseCookie cookie = ResponseCookie.from("SESSION", accessToken) //key값은 SESSION으로 사용하는 것이 일반적
                .domain("localhost") //로컬환경에서는 localhost, 개발에서는 dev.catnails.com 운영에서는 catnail.com 등. application.yml에 환경설증 후 구동 시 설정값을 넣어 구분해줄 수도 있다.
                .path("/")
                .httpOnly(true)
                .secure(false) //https사용 X
                .maxAge(Duration.ofDays(30)) //로그인 유지시간이나 다름없다. 기본 파라미터는 초단위다. 일반적으로는 30일을 사용한다.
                .sameSite("Strict")
                .build();
        //쿠키 자체를 리턴하지 않는다. ResponseEntity를 사용하여 거기에 쿠키를 담아 리턴한다.

        log.info(">>>>> cookie={}", cookie.toString());

        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, cookie.toString())
                .build();

    }
}

 

요청 테스트

### auth.http

POST http://localhost:8080/auth/login
Content-Type: application/json

{
  "email" : "catnails@gmail.com",
  "password" : "1234"
}

 

결과

응답 헤더에 Set-Cookie가 있는 것을 확인할 수 있다.

 

2023-02-16 22:17:19.252  INFO 17808 --- [nio-8080-exec-1] c.e.blog.controller.AuthController       : >>>>> cookie=SESSION=578f914d-0517-4e16-8c6b-9454ade04d1e; Path=/; Domain=localhost; Max-Age=2592000; Expires=Sat, 18 Mar 2023 13:17:19 GMT; HttpOnly; SameSite=Strict

//로그 역시 마찬가지

 

2. 인증 페이지 요청

쿠키 값을 응답받을 것을 확인할 수 있다.

브라우저라면 쿠키 값을 자동으로 쿠키를 저장하여 페이지를 이동하겠지만, 여기서는 브라우저를 사용하지 않으므로 직접 쿠키 값을 넣어서 요청해야 한다.

 

### 인증페이지 요청
GET http://localhost:8080/foo
Content-Type: application/json
Cookie: SESSION=578f914d-0517-4e16-8c6b-9454ade04d1e

당연히 실패한다. 왜냐하면 이제 Authroization을 사용하지 않고 Cookie를 사용하기 때문이다. 따라서 AuthResolver를 수정해 준다.

 

3. AuthResolver(=ArgumentResolver 수정)

//AuthResolver.java

package com.endofma.blog.config;

import com.endofma.blog.config.data.UserSession;
import com.endofma.blog.domain.Session;
import com.endofma.blog.exception.Unauthorized;
import com.endofma.blog.repository.SessionRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
public class AuthResolver implements HandlerMethodArgumentResolver {

    //주입
    private final SessionRepository sessionRepository;
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //컨트롤러에서 사용할 어노테이션이나 DTO가 사용자가 사용하려는 것이 맞는지, 지원하는지 체크한다.
        return parameter.getParameterType().equals(UserSession.class); //UserSession 클래스를 사용하는 것이 맞는지 확인. 맞으면 컨트롤러에 값 할당
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //실제로 해당 DTO에 값을 세팅해준다.

//        String accessToken = webRequest.getHeader("Authorization"); //헤더의 값으로 확인해준다.
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

        if (servletRequest == null) { //getNativeRequest는 nullable이다
            log.error("servletRequest null");
            throw new Unauthorized();
        }

        Cookie[] cookies = servletRequest.getCookies(); //배열로 받는다.
        if (cookies.length == 0) {
            log.error("쿠키가 없음");
            throw new Unauthorized();
        }

        String accessToken = cookies[0].getValue();

        Session session = sessionRepository.findByAccessToken(accessToken)
                .orElseThrow(Unauthorized::new);
        
//        return new UserSession(1L);
        return new UserSession(session.getUser().getId()); //컨트롤러로 넘겨준다.

    }
}

 

고쳤으면 서버 재기동.

 

4. 서버 재기동 후 쿠키 재발급

재기동했으니 쿠키를 다시 발급 받아야 한다.

### auth.http

POST http://localhost:8080/auth/login
Content-Type: application/json

{
  "email" : "catnails@gmail.com",
  "password" : "1234"
}
POST http://localhost:8080/auth/login

HTTP/1.1 200 
Set-Cookie: SESSION=41ab691f-b12f-47db-9e23-c8ad891fa496; Path=/; Domain=localhost; Max-Age=2592000; Expires=Sat, 18 Mar 2023 13:39:47 GMT; HttpOnly; SameSite=Strict
Content-Length: 0
Date: Thu, 16 Feb 2023 13:39:47 GMT
Keep-Alive: timeout=60
Connection: keep-alive

<Response body is empty>

Response code: 200; Time: 451ms (451 ms); Content length: 0 bytes (0 B)

Cookies are preserved between requests:
> D:\personal\blog\.idea\httpRequests\http-client.cookies

 

 

5. 인증페이지 요청

로그인 시 발급받은 쿠키 값을 적어주는 것에 유의

### 인증페이지 요청
GET http://localhost:8080/foo
Content-Type: application/json
Cookie: SESSION=41ab691f-b12f-47db-9e23-c8ad891fa496

성공

 

잘못된 쿠키를 적고 요청을 보내면 아래와 같은 응답이 반환된다.

 

### 인증페이지 요청
GET http://localhost:8080/foo
Content-Type: application/json
Cookie: SESSION=41ab691f-b12f-47db-9e23-c8ad891fa496--other

 

Comments