스프링 시큐리티는 다양하고 세밀한 보안 기능을 갖고 있습니다.
그 중 하나가 비지니스 로직을 포함한 메서드를 보호하기 위한 메서드 수준 보안입니다.
간단하게 어노테이션을 사용하여 권한에 따라 메서드를 보호할 수 있습니다
@EnableMethodSecurity
@EnableJpaAuditing
@SpringBootApplication
@EnableMethodSecurity
public class MinglecrmApplication {
public static void main(String[] args) {
SpringApplication.run(MinglecrmApplication.class, args);
}
}
메서드 수준 보안을 위해 가장 먼저 스프링 어플리케이션 클래스에 @EnableMethodSecurity 어노테이션을 적용 시켜야 합니다.
현재 Jwt를 사용해 인증을 수행하고 있습니다. 전체 로직을 보여주기에는 볼륨이 너무 커지므로 토큰 인증 필터 코드를 통해 설명하겠습니다.
import com.team2final.minglecrm.service.jwt.JwtProvider;
import com.team2final.minglecrm.controller.employee.vo.Subject;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JWTTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JWTTokenAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
if(authorization!=null) {
String atk = authorization.substring(7);
try {
Subject subject = jwtProvider.getSubject(atk);
String requestURI = request.getRequestURI();
if (subject.getType().equals("RTK") && !requestURI.equals("/api/v1/auth/renew")) {
throw new JwtException("토큰을 확인하세요");
}
String username = subject.getEmail();
String authorities = subject.getAuthority();
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (JwtException e) {
request.setAttribute("exception", e.getMessage());
}
}
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/user/signin");
}
}
위 코드에서 눈여겨 보아야 할 곳은 아래 부분입니다.
String authorities = subject.getAuthority();
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
첫 번째 줄에 위치한 String authorities = subject.getAuthority();를 사용하여 토큰에 포함된 페이로드에서 콤마(,)로 구분되는 권한 목록을 불러옵니다. 예를 들면 다음과 같습니다.
AuthorityUtils.commaSeparatedStringToAuthorityList 메서드는 "ROLE_MANAGER,ROLE_STAFF"라는 문자열을 받아 콤마로 구별한 후 SimpleGrantedAuthority객체 리스트로 변환시켜줍니다.
이렇게 만들어진 권한은 username(이메일)과 함께 Authentication객체를 상속한 UsernamePasswordAuthenticationToken객체에 담겨 SecurityContext에 저장되고 우리의 스프링 시큐리티 프레임워크가 이를 인지해 요청 승인/거절을 수행합니다
이제 테스트를 위한 RestAPI를 작성해보겠습니다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/authority/manager")
@PreAuthorize("hasRole('MANAGER')")
public ResponseEntity<String> authTestManager() {
return new ResponseEntity<>("요청 승인", HttpStatus.OK);
}
}
@PreAuthorize 어노테이션을 사용해 현재 인증된 사용자가 MANAGER라는 권한을 갖고 있을경우 메서드 요청을 승인하고, 그러지 않을 경우 차단합니다.
hasRole을 사용하고 있으므로 실제 권한은 접두사에 ROLE_이 붙어야합니다. 따라서 회원가입 시 ROLE을 붙혀주세요
atk에 있는 access token을 사용해 위에서 살펴본 API로 요청을 해봅시다.
이렇게 거절됩니다.
이번엔 회원가입 시 ROLE_MANAGER를 추가해서 요청을 보내보겠습니다.
마찬가지로 로그인 요청 후 토큰을 담아 요청을 보내보겠습니다.
보시는 것과 같이 요청이 승인됩니다.
메서드 수준 보안을 지원하기 위한 몇 가지 어노테이션이 더 존재합니다.
자세한 내용은 스프링 공식문서에서 확인하실 수 있습니다.
'SPRING > SECURITY' 카테고리의 다른 글
스프링 시큐리티 인증/인가 리팩토링 (0) | 2024.06.20 |
---|---|
[토이프로젝트] SpringSecurity6 + JWT + Redis 인증/인가 구현 (3) (0) | 2024.05.14 |
[토이프로젝트] SpringSecurity6 + JWT + Redis 인증/인가 구현 (2) (0) | 2024.05.14 |
[토이프로젝트] SpringSecurity6 + JWT + Redis 인증/인가 구현 (1) (0) | 2024.05.14 |