대부분의 서비스는 인증이 된 사용자만 이용이 가능합니다. 이번 포스팅에서는 스프링을 활용하여 기본적인 인증 방법을 소개하고자 합니다.
이번 포스팅에서는 다음과 같은 방법으로 인증 절차를 구현해보고자 합니다.
먼저 단순한 형태의 Get Parameter 입니다. ****
@PostMapping("/posts")
public void post(@RequestBody @Valid PostCreateDto request,
@RequestParam String authorization) {
if(authorization.equals("auth"){
request.isValidate();
postService.write(request);
}
}
이렇게 코드를 구현하게 되면 http://localhost:8080/posts?authorization = auth
형식으로 호출 해야만 글을 작성할 수 있습니다.
하지만 주소는 리소스를 식별하기 위한 방법인데 불필요하게 인증에 필요한 코드가 들어가면 좋지 않습니다.
Header를 이용한 인증 방법입니다.
@PostMapping("/posts")
public void post(@RequestBody @Valid PostCreateDto request,
@RequestHeader String authorization) {
if(authorization.equals("auth"){
request.isValidate();
postService.write(request);
}
}
헤더에 값이 제대로 가는지 어떻게 확인할까요? MockMvc를 통해 구현 가능합니다.
PostControllerTest.java
@Test
@DisplayName("글 작성시 헤더로 인증 값을 전달")
public write() throws Exception {
//given
PostCreateDto request = PostCreateDto.builder()
.title("블로그 제목")
.content("블로그 내용")
.build();
String json = objectMapper.writeValueAsString(request);
// when
mockMvc.perform(post("/posts")
.header("authorization", "auth")
.contentType(APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
.andDo(print());
블로그를 만든다고 가정 해봅시다. 블로그의 기능 중에서 인증이 필요한 기능은 무엇이 있을까요?
글 작성, 수정과 같이 블로그에 대부분을 차지하는 내용들은 블로그 주인만 작성할 수 있기에 인증이 필요합니다. 하지만 글 조회 같은 경우는 인증이 필요 없습니다.
그러면 글 작성, 수정, 그리고 삭제에 대한 메서드에 인증 절차를 추가한다고 가정해보겠습니다.
@PostMapping("/posts")
public void post(@RequestBody @Valid PostCreateDto request,
@RequestHeader String authorization) {
if(authorization.equals("auth"){
request.isValidate();
postService.write(request);
}
}
@PatchMapping("/posts/{postId}")
public PostResponse edit(@PathVariable Long postId,
@RequestBody @Valid PostEditor request,
@RequestHeader String authorization) {
return postService.edit(postId, request);
}
@DeleteMapping("/posts/{postId}")
public Post delete(@PathVariable Long postId,
@RequestHeader String authorization) {
return postService.delete(postId);
}
하지만 매번 인증이 필요한 메서드에 @RequestHeader
를 추가하는 것은 여간 귀찮은 일이 아닙니다.
이를 서술하기에 앞서 인터셉터가 무엇인지 짚고 넘어가야 될 것 같습니다.
**인터셉터**
는 “낚아채다” 라는 의미를 가지고 있는 영단어입니다. 그 의미와 걸맞게 인터셉터는 클라이언트에 의해 호출된 특정 URL이 컨트롤러에 도달하기 전 낚아채는 역할을 합니다. 따라서 개발자는 기존 **컨트롤러**
를 수정하지 않고 원하는 추가 작업을 사전 혹은 사후에 하여 컨트롤러를 제어할 수 있도록 도와주는 것이 **인터셉터**
입니다.
HandlerInterceptor
혹은 HandlerInterceptorAdapter
를 오버라이딩 함으로써 구현합니다.
HandlerInterceptor
는 총 세 개의 메서드를 가지고 있습니다.
preHandle()
boolean
이며 리턴이 true
일 경우 preHandle()
실행 후 핸들러에 접근합니다. false
일 경우 작업을 중단하기 때문에 컨트롤러와 남은 인터셉터가 실행되지 않습니다.postHandle()
View
가 생성되기 이전에 호출됩니다.ModelAndView
타입의 정보를 매개변수로 받기 떄문에 컨트롤러에서 View
정보를 전달하기 위해 작업한 Model
객체 정보를 참조 혹은 조작할 수 있습니다.preHandler()
가 false
인 경우 실행되지 않습니다.preHandler()
는 역순으로 호출됩니다.afterComplement()
그렇다면 인터셉터를 만들어보도록 하겠습니다.
AuthInterceptor.java
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String accessToken = request.getParameter("accessToken");
if (accessToken != null && accessToken.equals("auth")) {
return true;
}
throw new UnauthorizedException();
}
}
위 코드는 요청에 포함된 accessToken을 확인하여 인증이 되었는지 판단하는 메서드입니다. preHandle() 메서드에 해당 코드를 구현했기 때문에 컨트롤러 동작 이전에 인증 처리를 합니다.
만약 인증이 되지 않았다면UnauthorizedException
를 발생 시킵니다. 해당 예외는 401 상태코드를 반환합니다.
추가적으로 Interceptor를 등록해주기 위해서 WebMvcConfigurer를 구현한 설정 클래스에 등록해주어야 합니다.
WebMvcConfig.java
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor());
// .excludePathPatterns()
// .addPathPatterns();
}
}
+주석 처리된 두 메서드를 통해 인증에서 제외하거나 추가할 경로를 설정할 수 있습니다.