웹 애플리케이션을 개발하다 보면 로그인한 계정의 정보를 보여주는 마이페이지 등을 구현해야 할 때가 생긴다. 그런데 다른 계정의 토큰을 가진 사람이 여기에 접근하면 보안상 문제가 있기 때문에 컨트롤러 혹은 서비스에서 토큰 내부의 ID 값과 요청하는 ID 값을 확인하는 로직을 넣어주어야 한다.
문제 인식: 위처럼 요청하는 ID가 발급된 토큰 내부의 ID값과 일치하는지 확인하는 코드를 비즈니스 로직에 추가해주어야 한다. 그런데 비즈니스와는 상관없는 관심사 코드가 매번 들어가야 하므로 응집도가 떨어지는 단점이 있었다.
그래서 몇 가지 방법을 생각해봤다.
방법 1: token을 발급해주는 tokenProvider Bean 내부에서 검증해주는 로직을 작성하여 컨트롤러 or 서비스 내부에 넣는다.
-> 처음에 이 방법으로 했었는데 새로운 컨트롤러, 서비스를 만들 때마다 매번 tokenProvider를 주입받아야 하므로 단점을 덜어냈을뿐 근원은 해결하지 못했단 생각이 들었다.
방법 2: AOP를 사용하자.
-> 토큰 내부의 ID를 검증하는건 사실 관심사 밖이다. 스프링에선 이처럼 관심사 밖의 것들을 처리해주는 aop라는 도구가 있어 이걸 사용해보기로 했다.
--> securty에서 @preauthorize 애노테이션을 붙이면 해당 메서드를 수행하기 전 프록시로 감싸 검증을 수행한다. 또한 expression을 작성하여 유연하게 검증할 수 있다. 이 방식을 이용하여 ID 검증하는 기능을 @PreAuthorize을 사용하여 한 줄로 줄여보겠다.
스프링 시큐리티에서 @PreAuthorize를 사용하기 위해선 prePostEnabled를 활성화시켜야 한다.
@EnableGlobalMethodSecurity(prePostEnabled = true)
이제 TokenProvider 내부에 검증 로직을 작성해보자.
...
private String getAccountIdWithToken(String authValue) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(authValue.substring(7))
.getBody()
.getSubject();
}
public boolean isUserToken(String authValue, String accountId){
//authValue엔 Authorization 헤더 값이 들어있음
if(getAccountIdWithToken(authValue).equals(accountId)){
System.out.println("=========비교========");
System.out.println(getAccountIdWithToken(authValue) + " vs " + accountId);
return true;
}else{
return false;
}
}
원하는 컨트롤러 or 서비스 메서드에 어노테이션을 붙이자.
내부 식에 @를 붙이면 Bean을 가져올 수 있다.
@PreAuthorize("@tokenProvider.isUserToken(#authValue, #dto.fromAccountId)")
@PostMapping(value = "/tutoring/{tutoringId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> responseTutoring(
@RequestHeader(name = "Authorization") String authValue,
@PathVariable long tutoringId,
@Valid @RequestBody TutoringResponseDto dto) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(commandExecutor.responseTutoring(authValue, tutoringId, dto).getMessage());
}
결과
개선점: TokenProvider를 매번 주입받지 않고, 어노테이션 하나로 ID 검증 로직을 수행할 수 있게 되었다.
한계: 컴파일 타임에 에러를 잡아내지 못하고 런타임에 에러가 발생한다.. 주의해야 한다.
ref: https://developer.okta.com/blog/2019/06/20/spring-preauthorize
'Spring' 카테고리의 다른 글
스프링초짜의 도메인 이벤트 찍먹 도전기 (1) (0) | 2022.02.15 |
---|---|
ArgumentResolver로 토큰에서 값 추출하기 (0) | 2022.02.10 |
SpringBoot 중요한 설정 숨기기 (0) | 2022.01.12 |
SpringSecurity + CustomAuthenticationProvider 만들기 (0) | 2022.01.11 |
Filter, Interceptor, AOP 차이에 대한 정리 (0) | 2021.11.22 |