728x90

글을 시작하기에 앞서 쿠키, 세션, JWT 등 각 기술들의 특징을 알고 어떻게 써야하는지 깊게 고민해보신 분들은 제목부터 맘에 들지 않으실 수 있지만, 지금 작성하는 글은 '이런 기술들을 써봤고 구현은 이렇게 하더라!' 하는 것을 기록하는 용도이기에 왜 JWT를 쿠키에 담아서 했나요? 라고 질문주신다면 '그냥요', '한번도 안해봤던거라 재밌어 보여서요' 라고 밖에 드릴 답이 없습니다.


JSP로 화면을 구성하면서 로그인 관련 처리를 위해 JWT를 먼저 구현했었다. 그리고 로그인했을 때 정상적으로 토큰이 전달되어 로컬스토리지에 담기는 것 까지는 확인했으나 그 다음이 문제였다. 여태 했던 프로젝트들은 프론트와 백이 나누어져 있어서 프론트에서 토큰을 헤더에 담아 api를 요청하는 식으로 진행되었지만 문제가 발생한 프로젝트는 페이지 이동할 때 토큰을 어떻게 넘겨야하는지 몰라서 토큰이 로컬스토리지에 고이 보관만 되었다. API를 요청하는 경우에는 헤더에 토큰을 담을 수 있지만 단순 페이지를 이동하는 경우에는 어떻게 처리해야할지 몰라서 찾아보니 쿠키를 이용하면 자동으로 값들이 전달된다는 것을 알게되었다. 그래서 쿠키에 JWT를 담아서 전달하고 검증해보기로 했다.


먼저 간단한 로그인 컨트롤러부터 구현해 보았다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class RestAccountController {
    private final AccountService accountService;
    @PostMapping("/login")
        public ResponseEntity<?> restfulLogin(@Valid @RequestBody RequestLogIn request) {
        	// 토큰을 생성하는 로직(accountService.logInProcess(request).getToken())은 생략
            ResponseCookie cookie = ResponseCookie.from("jwt", accountService.logInProcess(request).getToken())
                    .httpOnly(true)
                    .path("/")
                    .maxAge(60 * 60 * 24)
                    .build();
            return ResponseEntity.ok()
                    .header(HttpHeaders.SET_COOKIE, cookie.toString())
                    .build();
        }
 }

그리고 JSP로 구현한 페이지에서 로그인을 시도하거나 POSTMAN같은 툴을 이용해서 API 테스트를 하면 응답의 쿠키속에 "jwt" 키 값을 가진 정보가 전달된 것을 확인할 수 있다.

 

 다음은 페이지 이동, API 요청 등이 발생했을 때 쿠키속에서 토큰을 추출하고 검증하는 코드를 구현해보았다.

@RequiredArgsConstructor
@Component
public class JsonWebTokenAuthenticationFilter extends OncePerRequestFilter {
    private final JsonWebTokenProvider tokenProvider;
    private final String ACCESS_TOKEN_NAME = "jwt";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Cookie token = parseBearerToken(request, ACCESS_TOKEN_NAME);
        if (token != null) {
            User user = parseUserSpecification(token.getValue());
            AbstractAuthenticationToken authenticated = UsernamePasswordAuthenticationToken.authenticated(user, token, user.getAuthorities());
            authenticated.setDetails(new WebAuthenticationDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticated);
        }

        filterChain.doFilter(request, response);
    }

    private Cookie parseBearerToken(HttpServletRequest request, String name) {
        return Optional.ofNullable(request.getCookies())
                .flatMap(cookies -> Arrays.stream(cookies)
                        .filter(cookie -> cookie.getName().equals(name))
                        .findFirst())
                .orElse(null);
    }
    
    ...
    
}

필터 소스코드의 전체는 아니지만 포스팅에 맞는 추출, 검증하는 부분만 가져왔다. parseBearerToken 매서드를 통해 쿠키속에서 원하는 key값과 매칭되는 값을 반환받은 뒤 그 값이 null 이 아닐경우 권한을 준다. 권한을 주는 과정에서 어떤 정보를 어떻게 담을 지는 프로젝트마다 사람마다 모두 다르기에 원하는대로 구현하면 된다.

 추가로 예전에 '개발바닥'이라는 유튜브 채널에서 'Java 8 이상을 사용하는데 stream을 사용하지 않으면 8버전 이상을 사용할 필요가 없다라는 말이 넌지시 나왔었고 이번 기회에 steam도 공부해보고자 사용해보았다. 

+ Recent posts