JWT로 로그인 처리한 서버에서 댓글을 작성하려고 한다.
Reply가 댓글 로직이라고 할 때 이 곳에서 토큰 검증이 필요하다.
(세션에는 저장된 정보가 없기 때문에 토큰 유효성검사만 이루어짐)
토큰을 검사하는 방법은 여러가지가 있다.
- 컨트롤러 메서드 마다 검사 로직 추가하기
- 인터셉터 활용하기
- AOP 활용하기
- 필터에서 처리
오늘은 필터에서 특정 url 이 붙은 경우 검사를 하도록 코드를 구성해 볼 것이다.
필터가 할 일은 다음과 같다.
- JWT 검증
- 세션에 저장
- 실패시 컨트롤러 진입 하지 않고 리턴.
1. 필터 구성전 살펴보기
ioc 등록 하고 메서드에 @bean 어노테이션 하면 메모리에 등록됨.
리턴값 등록위해 쓰기도함
@Configuration
public class FilterConfig {
public FilterConfig() {
System.out.println("FilterConfig");
}
@Bean
public User go(){
System.out.println("user Go");
return User.builder().id(1).build();
}
//
public FilterRegistration jwtAuthorizationFilter() {
return null;
}
}
코드 실행시
- @Bean 이
없는
경우 - FilterConfig 만 나옴
- @Bean 이
있는
경우 - FilterConfig 와 user Go 둘 다 나옴.
1.2 필터 코드 구성
애플리케이션이 실행될 때 언제 메서드가 메모리에 뜨는지 확인하였다.
이번에는 필터의 동작 예시를 살펴본다.
필터를 만들 때 리퀘스트, 리스폰스 둘 다 해놓고 만들어야 한다.
- 동작 예시 코드
public class JwtAuthorizarionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
System.out.println("JwtAuthorizarion 필터 동작");
PrintWriter out = response.getWriter(); //쓰기버퍼
System.out.println("<h1>good</h1>");
out.flush();
}
}
2. 필터 구성하기
- config 코드
@Configuration // ioc 등록 하고 메서드에 @bean 어노테이션 하면 메모리에 등록됨. 리턴값 등록위해 쓰기도함.
public class FilterConfig {
@Bean
public FilterRegistrationBean<JwtAuthorizationFilter> jwtAuthenticationFilter() {
FilterRegistrationBean<JwtAuthorizationFilter> bean
= new FilterRegistrationBean<>(new JwtAuthorizationFilter());
bean.addUrlPatterns("/api/*");
// 여기에 오름내림 차순 어떤걸로 시작할 지 골라야함. 마지막 번호 몰라서 처음부터 함.
bean.setOrder(0);
return bean;
}
}
- Filter 코드
public class JwtAuthorizationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain Chain) throws IOException, ServletException {
HttpServletRequest rq = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) response;
String accessToken = rq.getHeader("Authorization");
if (accessToken == null || accessToken.isBlank()) {
// resp 해줘야 함
resp.setHeader("Content-Type", "application/json; charset=utf-8");
PrintWriter out = resp.getWriter();
//통신은 객체를 던지면 안된다. fail은 자바객체이다! 필터에서는 인식을 못 한다.
Resp fail = Resp.fail(401, "Token이 없습니다.");
String responseBody = new ObjectMapper().writeValueAsString(fail);
out.print(responseBody);
out.flush();
return;
}
//try catch 를 쓴 이유는 항상 동일의 형태로 담아주기 위해서..
try {
User sessionUser = JwtUtil.verify(accessToken); // 서명이 위조, 만료
HttpSession session = rq.getSession();
session.setAttribute("sessionUser", sessionUser);
Chain.doFilter(rq, resp); // 다음 필터로 가!! 없으면 DS로 감.
} catch (Exception e) {
resp.setHeader("Content-Type", "application/json; charset=utf-8");
PrintWriter out = resp.getWriter();
//통신은 객체를 던지면 안된다. fail은 자바객체이다! 필터에서는 인식을 못 한다.
Resp fail = Resp.fail(401, e.getMessage());
String responseBody = new ObjectMapper().writeValueAsString(fail);
out.print(responseBody);
out.flush();
}
}
}
3. Postman 활용하여 확인
- 확인

url 이
api
가 들어가 있다면 controller 에 가기전, 먼저 Token 부터 확인한다.- 로그인 해야 토큰이 발급된다.


4. 댓글 작성
지금 토큰은 D/S 앞에서 값을 검증하고 있다.
댓글을 달기 위해서는
유저 토큰 확인 → 컨트롤러에서(해당 유저 정보 체크) → 등등…
과정이 필요한데, 이를 위해 또 다시 토큰을 검증하기는 일이 많아진다.
따라서 토큰을 확인할 때 세션에 잠시 저장을 시켜버린다.
- JwtUtil
public static User verify(String jwt){
jwt = jwt.replace("Bearer ", "");
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512("metacoding")).build().verify(jwt);
int id = decodedJWT.getClaim("id").asInt();
String username = decodedJWT.getClaim("username").asString();
return User.builder()
.id(id)
.username(username)
.build();
}
- 댓글 쓰기 요청
상단의 리스폰스 헤더에서 확인한 Authorization 값을 요청 헤더에 넣어준다.

DTO 형식에 맞게

요청을 보내면 Body 에서 리턴된 저장된 값을 확인할 수 있고,
만들어진 쿠키를 확인하면
JwtUtil
에서 저장한 값이 들어가 있음을 알 수 있다.
이렇게 insert 나 update 된 데이터를 리턴하는게 restAPI의 규칙이다.
저장된 값은 H2 콘솔가면 확인가능
실패 확인


이것때문에 예외가 잡아진다.
만약 해당 코드가 없으면 서버 계속 터지는거.
- 서버가 공격받는다면
존재하지 않는 게시글 번호로 10000번 댓글 작성요청을 보낸다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
public class Attack {
public static void main(String[] args) {
String targetUrl = "http://127.0.0.1:8080/api/reply"; // 요청할 URL
String jsonInputString = """
{"boardId":100, "comment":"댓글이다"}
""";
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
try {
URL url = new URL(targetUrl);
// HttpURLConnection 객체 생성
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 요청 방식 설정 (POST)
conn.setRequestMethod("POST");
// 요청에 JSON 형식으로 데이터를 보낸다고 명시
conn.setRequestProperty("Content-Type", "application/json; utf-8");
conn.setRequestProperty("Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiLrsJTrs7QiLCJpZCI6MSwiZXhwIjoxNzI3NjcyMDQ1LCJ1c2VybmFtZSI6InNzYXIifQ.XJTahPFEbvy8a_RoftAKctxNcknVMec6regFKhqgFKIbfSeUPxCY7ygPNzdEoOvffOcd5eZX0wrTijwIcT2sqA");
conn.setDoOutput(true);
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.println(jsonInputString);
out.flush();
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream())
);
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}

local로 요청했기 때문에 별 일 없었지만 만약 서비스 환경이었다면 이 요청을 처리하는 동안 다른 클라이언트의 요청처리가 불가능 했을 것이다.
이런 공격을 막기 위해 같은 ip 에서 반복적으로 요청하는 것을 확인하여 처리할 수 있다.
코드를 짤 수 있고, 전문으로 처리해 주는 사이트에서 제공을 받을 수 있다.
(Cloudflare 도 있음 - 리버스 프록시 처럼 사용됨)

마무리하며
rest api 서버 로 만들려면 단순히 model 하여 페이지 리턴하는 것 삭제,
값이 들어있다면 body 에
insert
update
한 값을 담아서 프론트로 넘겨야한다Share article