API 요청 시 접근하는 URI에 자신의 id 값을 입력하여
접근을 하는데 이 때 id가 본인이 맞는지 확인하는 방법이 무엇일까?
의문
--
회원 A의 id(기본키) 값이 5라고 가정하자.
회원 A는 마이 페이지에 접근하려고 마이페이지에 접근하는 API URI인
/member/{id}/myPage 에 접근을 했다고 할 때
일반 회원이 조작없이 접근할 때는 자동으로 URI가 완성되어 자신의 id를 입력하여 접근이 되지만
만약 본인의 id가 5지만 URI는 6으로 변경하여 수동으로 요청을 보내게 되면 어떻게 될까?
우선 JWT의 토큰으로 회원이 인증이 되므로 접근이 가능해진다.
그리고 API에 접근할 때는 id의 값을 본인의 아이디가 아닌 다른 아이디로 접근을 하여
본인이 아닌 다른 사람의 정보를 볼 수 있게 된다.
--
해결 방법 찾기
--
1. @PreAuthorize() 어노테이션을 이용하기
해당 어노테이션은 메서드(API) 실행 전에 보안 검사를 먼저 수행하는 데에 사용된다.
Spring Security 모듈과 함께 사용되며 메서드(API)가 호출되기 전에 특정한 권한 또는 조건을 충족하는지를 확인한다.
해당 어노테이션을 사용하면 편리하게 검증을 할 수 있지만
만약 검증을 해야하는 API가 많다면 각자 모두 작성해줘야 하는 불편함이 생길 것 같다.
2. 해당 API의 Controller 또는 Service에 로직을 구현하기
컨트롤러나 서비스의 내부에서 직접 로직을 구현하는 것으로
요청받은 URI에 작성된 id 값을 추출해오고
가팅 요청 받은 토큰 안에 저장된 id 값을 추출해와서 서로 비교하는 방법이다.
해당 방법 또한 검증이 필요한 API 마다 구현을 해줘야하고 직접 로직까지 작성을 해야하므로
@PreAuthorize() 어노테이션을 사용하는 것 보다 많은 작업을 해야할 것으로 보인다.
3. API의 URI에 id를 작성하여 사용하지 않고 직접 토큰에 저장된 id를 사용하기
API의 URI에서 id를 작성하여 해당 id로 회원을 구분하지 않고
토큰안에 저장된 id를 꺼내와서 회원을 구분하는 방법이다.
즉, /member/{id}/myPage 이렇게 작성해서 id를 이용하는 방법 말고
/member/myPage로 URI를 사용하고 id는 토큰에 저장되어 있는 회원 id를 꺼내와서 사용하는 방법이다.
이는 Controller나 Service에서 토큰을 받아온 다음 해당 토큰에서 id값을 추출하는 방법으로 하면 될 것 같다.
그러면 해당 방법도 각 API마다 해당 로직을 작성해야 될 것으로 보인다.
4. 스프링 시큐리티에서 필터 내부에서 URI의 id와 토큰의 id 값 검증하기
시큐리티 필터에서 접근한 URI를 가져와서 id 부분을 추출하고
토큰에서 id 값을 추출하여 서로 검증하는 방법이다.
이는 시큐리티 필터에서 검증을 진행하는 것으로 컨트롤러에 접근하기전에 검증을 하기 때문에
검즘에 실패하면 컨트롤러에 접근하기 전에 차단을 할 수 있게 된다.
해당 방법을 사용하면 각 API마다 로직을 작성할 필요없이 시큐리티 필터에 URI에 id가 들어간 경우와 토큰을 검증에 통과한 경우에만 서로 id를 검증하는 로직을 하나 작성하기만 하면 더이상 로직을 작성할 필요가 없어보인다.
--
해결 방법 선택하기
--
로직을 작성하지 않고 가장 편한 방법은 @PreAuthorize() 어노테이션을 사용하는 방법이지만
id 검증이 필요한 API마다 작성을 해줘야 하므로
한 번 로직을 작성하고 검증이 필요한 API에 모두 적용할 수 있는
스프링 시큐리티의 필터에 id를 검증하는 방법을 선택하기로 했다.
--
스프링 시큐리티 필터에 id 검증하는 코드 작성하기
--
CustomJwtFilter.java
@Slf4j
@RequiredArgsConstructor
public class CustomJwtFilter extends OncePerRequestFilter {
private final CustomUserDetailsService customUserDetailsService;
private final JwtFunction jwtFunction;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 헤더에 "Authorization"와 "Refresh_Token"에 담긴 값을 가져온다. (토큰)
String header_access = request.getHeader(JWTProperties.HEADER_STRING);
String header_refresh = request.getHeader(JWTProperties.REFRESH_STRING);
// 토큰이 존재하는지 검사 확인
// 토큰 유요성 검사
// 토큰에 있는 값을 가지고 해당 회원이 존재하는지 확인
// 토큰의 회원 id와 URI의 id가 일치한지 검증
String str = request.getRequestURI();
log.info("{}", str);
if(str.startsWith("/member")){
// 토큰에서 회원 id 추출
Long memberId = jwtFunction.getMemberId(jwtToken);
// uri에서 id 추출과 동시에 id 비교
getIdFromRequest(str, memberId);
}
// 나머지 토큰 검증
// refreshToekn 검증
filterChain.doFilter(request, response);
}
// URI에 작성한 id와 토큰에서 id 추출하여 같은지 비교하기
private void getIdFromRequest(String uri, Long id){
String[] str = uri.split("/");
log.info("{}", str[2]);
String tokenId = id.toString();
if (!str[2].equals(tokenId)){
throw new IllegalArgumentException("인증된 회원 id와 다른 id로 접근하였습니다.");
}
}
}
--
'Self Q&A' 카테고리의 다른 글
[Spring Boot] 요청 받을 파라미터(@RequestParam)이 많으면? (0) | 2024.04.28 |
---|---|
DTO를 깔끔하게 정리하는 방법이 없을까? (0) | 2024.04.25 |
JWT를 사용할 때 보안을 높이는 방법은 무엇이 있을까? (0) | 2024.04.19 |
로그인 인증 Session과 JWT 중에 무엇을 사용할까? (0) | 2024.04.17 |