[Spring] 스프링 시큐리티 테스트

WOOK JONG KIM·2022년 11월 9일
0
post-thumbnail

클라이언트의 입장에서 스프링 시큐리티가 동작하는 상황에 대해 테스트 수행(Swagger 활용)
-> 접속 경로는 WebSecurity를 사용하는 configure() 메서드에서 인증에 대한 예외 처리 했기에 접속 가능

애플리케이션 가동 로그

애플리케이션이 가동되면 스프링 시큐리티와 관련된 빈도 초기화되어 등록되면서 몇가지 로그 확인 가능

jwtTokenProvider

[2022-11-09 15:11:42.313] [INFO ][main] com.springboot.security.config.security.JwtTokenProvider [init] JwtTokenProvider 내 secretKey 초기화 시작
[2022-11-09 15:11:42.313] [INFO ][main] com.springboot.security.config.security.JwtTokenProvider [init] JwtTokenProvider 내 SecretKey 초기화 완료

jwtTokenProvider 클래스는 @Component로 등록돼 있고 @PostConstruct로 init() 메서드가 정의돼 있음

init() 메서드에서 properties에 정의되 있는 secretkey의 값을 가져와 인코딩 하는 작업 수행

DefaultSecurityFilterChain


[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/v2/api-docs']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/v2/api-docs']
[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/swagger-resources/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/swagger-resources/**']
[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/swagger-ui.html']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/swagger-ui.html']
[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/webjars/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/webjars/**']
[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/swagger/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/swagger/**']
[2022-35-09 15:35:08.923] [WARN ][main] org.springframework.security.config.annotation.web.builders.WebSecurity You are asking Spring Security to ignore Ant [pattern='/sign-api/exception']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
[2022-35-09 15:35:08.923] [INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will not secure Ant [pattern='/sign-api/exception']
[2022-11-09 15:11:42.600] 
[INFO ][main] org.springframework.security.web.DefaultSecurityFilterChain Will secure any request with 
[
org.springframework.security.web.session.DisableEncodeUrlFilter@28b47211, 
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4abb4041, 
org.springframework.security.web.context.SecurityContextPersistenceFilter@6b3da23f, 
org.springframework.security.web.header.HeaderWriterFilter@df34b01, 
org.springframework.security.web.csrf.CsrfFilter@77185a2, 
org.springframework.security.web.authentication.logout.LogoutFilter@43c64d6f, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@17f7a1af, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@742aa00a, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@247415be, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@377cc0f8, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5058fefb, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@198a0416, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@48dc9950, 
org.springframework.security.web.session.SessionManagementFilter@f31b991, 
org.springframework.security.web.access.ExceptionTranslationFilter@cce672c, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@e280006
]

이는 SercurityFilterChain 인터페이스의 구현체 클래스

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
    private final RequestMatcher requestMatcher;
    private final List<Filter> filters;

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
        this(requestMatcher, Arrays.asList(filters));
    }

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        if (filters.isEmpty()) {
            logger.info(LogMessage.format("Will not secure %s", requestMatcher));
        } else {
            logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
        }

        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList(filters);
        
   // 생략....
    }
}

DefaultSecurityFilterChain은 HttpSecurity에 의해 호출되며, 생성자를 통해 사용될 Filter를 전달받음

	@Override
    public void configure(WebSecurity webSecurity){
        webSecurity.ignoring().antMatchers("/v2/api-docs", "/swagger-resources/**",
                "/swagger-ui.html", "/webjars/**", "/swagger/**", "/sign-api/exception");
    }

앞서 정의한 SecurityConfiguration 클래스에서 설정한 WebSecurity를 활용하는 configure() 메서드에서 제외한 경로 표현

이후 앞서 보았던 DefaultSecurityFilterChain로그는 제외된 경로 외의 모든 요청에 대해 나열된 필터를 거친다는 것을 의미


정상적인 동작 시나리오

  1. 회원가입 성공
  2. 회원가입에 성공한 게정 정보를 기반으로 로그인을 성공
    -> 로그인에 성공하면서 토큰을 발급받음
  3. 상품 컨트롤러의 상품 등록 API를 호출
    -> API 호출 시 로그인 과정에서 받은 토큰을 헤더에 추가해서 전달
  4. 정상적으로 상품 등록을 마침

정상적으로 회원가입 화면 출력 후 로그인 수행

로그인이 정상적으로 수행되었고 응답으로 온 토큰 값 또한 볼 수 있음

Swagger 페이지를 통한 상품 등록하기 위한 헤더 값 입력할 수 있는 폼 추가

@ApiImplicitParams({
            @ApiImplicitParam(name = "X-AUTH-TOKEN", value = "로그인 성공 후 발급 받은 access_token",
            required = true, dataType = "String", paramType = "header")
    })
    @PostMapping()
    public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
        ProductResponseDto productResponseDto = productService.saveProduct(productDto);

        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }

create Product


비정상적인 동작 시나리오 - 인증 예외 발생

비정상적인 동작 크게 두가지 -> 인증 실패, 인가 실패

  1. 회원가입에 성공한다
  2. 회원가입에 성공한 계정 정보를 기반으로 로그인에 성공한다
  3. 상품 컨트롤러의 상품 등록 API를 호출한다 -> 호출시 토큰을 변조해서 헤더에 추가
  4. 인증 예외 메세지

인증과정 에서 예외 발생 경우

인증이 실패하여 CustomAuthenticationEntryPoint에 구현한 예외 상항에 대한 메세지가 담긴 응답이 애플리케이션에서 생서되고 클라이언트에게 전달되었음

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ObjectMapper objectMapper = new ObjectMapper();
        LOGGER.info("[commence] 인증 실패로 response.sendError 발생");

        EntryPointErrorResponse entryPointErrorResponse = new EntryPointErrorResponse();
        entryPointErrorResponse.setMsg("인증이 실패하였습니다");

        response.setStatus(401);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(entryPointErrorResponse));
    }
}

인가 예외 상황

[2022-49-09 16:49:12.990] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 추출 완료. token : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmZHNhMTIzNCIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE2Njc5ODAwODgsImV4cCI6MTY2Nzk4MzY4OH0.OsCBVrNSIX1MuOtpZwCpqA7RUrin80x2_VRHKg8Ik0c
[2022-49-09 16:49:12.990] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 유효성 체크 시작
[2022-49-09 16:49:12.990] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [validateToken] 토큰 유효 체크 시작
[2022-49-09 16:49:12.993] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [validateToken] 토큰 유효 체크 완료
[2022-49-09 16:49:12.993] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [getAuthentication] 토큰 인증 정보 조회 시작
[2022-49-09 16:49:12.993] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [getUsername] 토큰 기반 회원 구별 정보 추출
[2022-49-09 16:49:12.994] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [getUsername] 토큰 기반 회원 구별 정보 추출 완료, info : fdsa1234
[2022-49-09 16:49:12.994] [INFO ][http-nio-8080-exec-8] com.springboot.security.service.impl.UserDetailsServiceImpl [loadUserByUsername] loadUserByUsername 수행. username : fdsa1234


[2022-49-09 16:49:13.002] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtTokenProvider [getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : fdsa1234
[2022-49-09 16:49:13.003] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 유효성 체크 완료
[2022-49-09 16:49:13.005] [INFO ][http-nio-8080-exec-8] com.springboot.security.config.security.CustomAccessDeniedHandler [handle] 접근이 막혔을 경우 경로 리다이렉트
[2022-49-09 16:49:13.009] [ERROR][http-nio-8080-exec-9] com.springboot.security.controller.SignController ExceptionHandler 호출, null, 접근이 금지되었습니다.

인증은 정상적으로 수행되었지만 인가가 실패해서 접근이 막힘
-> CustomAccessDeniedHandler에서 수행


최종적인 로그 수행 순서

com.springboot.security.service.impl.SignServiceImpl [getSignUpResult] userEntity 값이 들어왔는지 확인 후 결과 주입
com.springboot.security.service.impl.SignServiceImpl [getSignUpResult] 정상 처리 완료
com.springboot.security.controller.SignController [signUp] 회원가입을 완료했습니다. id : fdsa1515


com.springboot.security.config.security.JwtTokenProvider [resolveToken] HTTP 헤더에서 Token 값 추출
com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 추출 완료. token : null
com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 유효성 체크 시작
com.springboot.security.controller.SignController [signIn] 로그인을 시도하고 있습니다. id : fdsa1515, pw : ****
com.springboot.security.service.impl.SignServiceImpl [getSignInResult] signDataHandler로 회원 정보 요청

com.springboot.security.service.impl.SignServiceImpl [getSignInResult] Id : fdsa1515
com.springboot.security.service.impl.SignServiceImpl [getSignInResult] 패스워드 비교 수행
com.springboot.security.service.impl.SignServiceImpl [getSignInResult] 패스워드 일치
com.springboot.security.service.impl.SignServiceImpl [getSignInResult] SignInResultDto 객체 생성
com.springboot.security.config.security.JwtTokenProvider [createToken] 토큰 생성 시작
com.springboot.security.config.security.JwtTokenProvider [createToken] 토큰 생성 완료
com.springboot.security.service.impl.SignServiceImpl [getSignInResult] SignInResultDto 객체에 값 주입
com.springboot.security.controller.SignController [signIn] 정상적으로 로그인되었습니다. id : fdsa1515, token : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmZHNhMTUxNSIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiaWF0IjoxNjY3OTgwNzUxLCJleHAiOjE2Njc5ODQzNTF9.x0UqFz5dpGSyt_msHSjVoyggMiZBNzX9_OzVYcO9jEQ

com.springboot.security.config.security.JwtTokenProvider [resolveToken] HTTP 헤더에서 Token 값 추출
com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 추출 완료. token : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmZHNhMTUxNSIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiaWF0IjoxNjY3OTgwNzUxLCJleHAiOjE2Njc5ODQzNTF9.x0UqFz5dpGSyt_msHSjVoyggMiZBNzX9_OzVYcO9jEQ
com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 유효성 체크 시작
com.springboot.security.config.security.JwtTokenProvider [validateToken] 토큰 유효 체크 시작
com.springboot.security.config.security.JwtTokenProvider [validateToken] 토큰 유효 체크 완료
com.springboot.security.config.security.JwtTokenProvider [getAuthentication] 토큰 인증 정보 조회 시작
com.springboot.security.config.security.JwtTokenProvider [getUsername] 토큰 기반 회원 구별 정보 추출
com.springboot.security.config.security.JwtTokenProvider [getUsername] 토큰 기반 회원 구별 정보 추출 완료, info : fdsa1515
com.springboot.security.service.impl.UserDetailsServiceImpl [loadUserByUsername] loadUserByUsername 수행. username : fdsa1515
com.springboot.security.config.security.JwtTokenProvider [getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : fdsa1515
com.springboot.security.config.security.JwtAuthenticationFilter [doFilterInternal] token 값 유효성 체크 완료
com.springboot.security.service.impl.ProductServiceImpl [saveProduct productDTO : ProductDto(name=string, price=12, stock=132)
com.springboot.security.service.impl.ProductServiceImpl [saveProduct] saveProduct : Product(number=2, price=12, stock=132, createdAt=null, updatedAt=null)
profile
Journey for Backend Developer

0개의 댓글