
시큐리티를 공부하면서 가장 직관적으로 잘 와닿은 인증 프로세스 이미지를 첨부했다.
Spring은 Username과 Password 기반의 로그인 프로세스가 내장되어있지만 jwt를 활용하여 인증처리로 확장을 하기 위해선 기존의 프로세스 일부를 오버라이딩하여 커스터마이징하는 방법을 제시하고 있다.
- Spring Security란? 스프링 시큐리티는 자바 애플리케이션에 인증과 인가를 제공하는 데 중점을 둔 프레임워크라고 정리할 수 있다.
- 특징으로 손꼽는 것은 확장성인데, 기존 서블릿 필터에서 인증처리를 한 것보다 customizing으로 맞춤형 요구사항을 충족시키며 쉽게 확장할 수 있다. 예를 들어 기본적인 security방식은 세션쿠키방식으로 인증하지만, jwt를 활용하여 접근을 인증하는 방식으로 확장할 수 있다.
* 용어정리

1) SecurityContextHolder
SecurityContextHolder는 SecurityContext를 제공하는 static 메소드(getContext)를 지원한다.
2) SecurityContext
SecurityContext 는 접근 주체와 인증에 대한 정보를 담고 있는 Context 이다.
즉, Authentication 을 담고 있다.
3) Authentication
Principal과 GrantAuthority를 제공한다.
인증이 이루어 지면 해당 Athentication이 저장된다.
4) Principal
유저에 해당하는 정보입니다.
대부분의 경우 Principal로 UserDetails를 반환한다.
5) GrantAuthority
ROLE_ADMIN, ROLE_USER 등 Principal이 가지고 있는 권한을 나타낸다..
- 구성요소 역할
- Filter Chain : 스프링시큐리티에는 다양한 Filters로 구성되어 있다. Request 정보가 Controller 계층에 도달하기 전에 filter chain에서 먼저 미리 정해진 절차대로 처리한다.
- UsernamePasswordAuthenticationToken : Spring Security에서 사용자 인증 정보를 표현하는 데 사용되는 클래스로, 인증 과정에서 사용자의 세부 정보와 권한 정보를 담고있다.
- AuthenticationManager : Authentication 객체를 매개변수로 받아 인증 과정을 수행하는 핵심 인터페이스이다. 인증에 성공하면 권한 정보가 포함된 새로운 Authentication 객체를 반환하고 이 객체는 Security Context에 저장되어 인증성공한 사용자 목록에 포함시킨다.
- AuthenticatonProvider : manager가 매개변수로 전달한 authentication 객체를 검증하는 인터페이스이다. UserDetailsService~ User 객체까지의 과정을 통해 사용자 이름, 비밀번호, 토큰 등을 검증하고 다시 manager에게 authentication 객체를 반환한다.
- Security Context Holder : 결국 인증에 성공한 authentication 객체(보통 사용자 id, 비밀번호, role 정보가 담긴다) 가 Context 에 저장하고, 이후부턴 발급된 토큰을 통해 다른 api를 요청하면 Context 안의 인증된 사용자 정보와 비교해서 인증 결과를 바탕으로 서비스를 제공할지, 거부할지가 결정된다.
- 예외 처리에서 주의해야할 점.
개발을 진행하다보면 토큰이 만료된다던지, 토큰이 잘못 들어온다던지, 토큰이 존재하지 않는다던지 등 다양한 이유로 인해 Exception이 발생할 수 있다. 일반적으로 발생하는 exception은 controller - service - repository 단에서 발생하기 때문에 @ControllerAdvice 어노테이션을 붙인 예외처리 클래스를 정의해두고 @ExceptionHandler 어노테이션을 통해 특정 예외 발생시에 대한 대처 방법을 정의한다.
하지만 Spring Security를 도입한 이후부턴 예외처리를 해야할 곳이 두 군데로 늘어난다. 사용자 인증 인가 과정에서 발생하는 Exception은 Controller 계층에 들어가기 이전에 발생하기 때문에, ExceptionHandler로 아무리 정의한다고 해도 원하는대로 응답을 받지 못한다. Security 프로세스 중 에러가 발생하면, Servlet이 자체적으로 response를 반환해버리기 때문이다. 이 부분을 확실하게 이해하는데 시간이 꽤 걸렸다.
그래서 원하는 에러메세지 타입으로 커스텀을 진행하고자 할 땐 다음과 같이 진행해야 한다.
Security process 에서 제공하는 예외처리 핸들러를 활용하는 것이다. AccessDeniedHandler와 AuthenticationEntryPoint 인터페이스를 구현하여 활용해야한다.
멤버등급에 맞지 않는 api에 접근할 때의 예외처리 핸들러를 정의했다.
@Component
@Slf4j(topic = "FORBIDDEN_EXCEPTION_HANDLER")
@RequiredArgsConstructor
public class CustomAccessDeniedHandler implements AccessDeniedHandler{
private final ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws ServletException, IOException{
log.error(accessDeniedException.getMessage());
ErrorMessageResponseDto responseMsg = new ErrorMessageResponseDto(ApiStatus.FAIL, "접근이 거부되었습니다.","ACCESS_DENIED");
String responseBody = objectMapper.writeValueAsString(responseMsg);
response.setContentType("application/json;charset=UTF-8");
response.setStatus(ErrorMessage.ACCESS_DENIED.getCode());
response.getWriter().write(responseBody);
}
}
ErrorMessage는 기존에 FE와 약속된 리턴타입으로 맞춰주기 위해 필요한 정보만 담아서 커스터마이징 한 클래스이다.
AccessDeniedHandler의 단일메소드를 오버라이딩하여 코드를 입맛에 맞게 작성하면, 원하는 대로 응답값을 받을 수 있다.
- 권한 세분화 작업
구현중인 프로젝트에는 ADMIN과 USER로 등급이 나뉜다. 세부적으로 보면 관리자도 SUPER 관리자와 중간등급의 관리자 로우 등급의 관리자로 또 구분이 된다. 특히 ADMIN 페이지들의 경우 USER는 접근이 되어선 안된다. 반대로 ADMIN 계정으로는 USER들이 접속하는 페이지에는 접근이 안되게 막아야한다.
다양한 기능을 공부하고 내린 결과를 설명하려고 한다.
우선 hasRole메소드에 대해서 알아야한다.
enum class로 ROLE_USER, ROLE_SUPER, ROLE_INTER, ROLE_LOW 4가지로 등급을 구분한다고 했을 때, 각각의 아이디와 비밀번호로 로그인을 하게 되면, Spring Context 내부에는 4개의 인증성공한 객체가 있을 것이고, 각각의 객체 내부에는 권한 정보도 포함되어 있다.
filterchain 권한 규칙을 생성하는 메소드인 authorizeHttpRequests() 내부에서 hasRole 혹은 hasAnyRole을 사용한다.
.requestMatchers("/api/admin/**").hasAnyRole("SUPER","INTER", "LOW") //admin api는 관리자 계정만
.requestMatchers("api/user/**").hasRole("USER")
다음과 같이 코드를 작성하게 되면 ADMIN API는 관리자 계정으로 로그인했을 때만, USER API는 일반회원 계정으로 로그인했을 때만 접근이 가능하다.
이후에 Controller 계층에서 메소드별로 더 세분화가 가능하다. 바로 @PreAuthorize 를 활용하는 것이다.
@PostMapping("/write")
@PreAuthorize("hasRole('SUPER')")
public ResponseEntity<ApiResponseWrapper> createGptContent(@RequestBody CreateContentRequestDto createGptContentRequestDto) {
return adminService.write(createContentRequestDto);
}
admin 페이지도 다양한 api가 있다보니 특정 메소드는 모든 관리자가 접근할 수 있게, 특정 메소드는 슈퍼관리자만 접근할 수 있게 조치를 취해야하는 경우가 존재한다. 위와 같이 코드를 작성하면 'SUPER' 관리자만 접근이 가능하게 된다.
- 실습 내용
h2데이터베이스를 활용해서 코드개발을 진행했다. db에는 상급,중급,하급 관리자 계정과 유저 계정 총 4개가 존재한다.
일단 각각의 계정으로 로그인을 하게 되면 Spring Context에는 4개의 인증성공한 authentication 객체를 가지게 된다. FE는 액세스토큰과 리프레시토큰을 받게 된다.
1. 정상적인 로그인 (SUPER 관리자로 로그인)

2. 중복되는 이메일로 회원가입

3. ADMIN 관리자가 USER API에 접근했을 때

4. 로그인 유무와 무관하게 접근해야하는 API ( User의 access token이 들어있든, bearer token이 비어있든 접근가능... 관리자 Access token 제외!)

5. 토큰이 만료되었을 때

'Backend' 카테고리의 다른 글
| [Springboot] Postmapping vs Putmapping vs Patchmapping (1) | 2024.04.08 |
|---|---|
| [Spring Security] 카카오 Auth2.0 기반 인증인가 프로세스 적용 (0) | 2024.03.28 |
| [Spring Security] FilterChain, ExceptionHandler 커스터마이징2 (0) | 2024.02.23 |
| [Spring Security] FilterChain, ExceptionHandler 커스터마이징 (4) | 2024.02.21 |
| [Springboot] 한국수출입은행 환율 api 활용하여 환율정보 가져오기 (1) | 2024.02.08 |