글을 시작하기에 앞서 쿠키, 세션, 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도 공부해보고자 사용해보았다.
'코딩 공부 > SpringBoot' 카테고리의 다른 글
스프링 시큐리티 - 회원가입, 로그인, 권한별 페이지 접근 설정 (0) | 2024.01.20 |
---|---|
Controller 에서 Form-data를 받는 방법들 (0) | 2024.01.15 |
스프링부트 테스트용 DB분리 (0) | 2023.11.21 |
스프링 부트 + 도커(docker, docker-compose) (1) | 2023.11.06 |
스프링부트 + 마이바티스 연동 (1) | 2023.11.02 |