본문 바로가기

IT

JWT와 Security를 활용한 인증, 인가 구현 2 ( Security 필터 추가)

728x90

1. SecurityConfig를 작성하자. 

JwtAuthorizationFilter 라는 필터를 생성하여 스프링 시큐리티 내부에 여러 인증 필터 중 만만한 UsernamePasswordAuthenticationFilter 앞에 추가하였다. 

@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    http
            .antMatcher("/api/**")
            .exceptionHandling(exceptionHandling -> exceptionHandling
                    .authenticationEntryPoint(authenticationEntryPoint)
            )
            .httpBasic().disable()
            .csrf().disable()
            .cors(cors -> cors
                    .configurationSource(corsConfigurationSource())
            )
            .authorizeRequests(
                    authorizeRequests -> authorizeRequests
                            .antMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
                            .antMatchers("/api/*/member/login").permitAll()
                            .anyRequest()
                            .authenticated() // 최소자격 : 로그인
            )
            .sessionManagement(sessionManagement -> sessionManagement
                    .sessionCreationPolicy(STATELESS)
            )
            .formLogin().disable()
            .addFilterBefore(
                    jwtAuthorizationFilter,
                    UsernamePasswordAuthenticationFilter.class
            )
            .logout().disable();

    return http.build();
}

 

2. 추후 REST API를 구현할 떄 CORS 문제를 미리 차단하기 위한 CorsConfigurationSource도 추가하였다.

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();

    corsConfiguration.addAllowedOrigin("*");
    corsConfiguration.addAllowedHeader("*");
    corsConfiguration.addAllowedMethod("*");

    UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
    urlBasedCorsConfigurationSource.registerCorsConfiguration("/api/**", corsConfiguration);
    return urlBasedCorsConfigurationSource;
}

 

3. JwtAuthorizationFilter 함수를 어떻게 구현하였는지 확인해보자. OncePerRequestFilter 를 상속받고 doFilterInternal 함수를 오버라이드받아 구현하면 된다. 들어온 요청에 대해 token을 분리하고 이전에 작성한 Jwt 관련 함수들을 활용했다. 

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    log.debug("JwtAuthorizationFilter 실행됨");
    String bearerToken = request.getHeader("Authorization");

    if (bearerToken != null) {
        String token = bearerToken.substring("Bearer ".length());
        // 1차 체크(정보가 변조되지 않았는지 체크)
        if (jwtProvider.verify(token)) {
            Map<String, Object> claims = jwtProvider.getClaims(token);
            Member member = memberService.getByUsername__cached((String) claims.get("username"));
            // 2차 체크(화이트리스트에 포함되는지)
            if ( memberService.verifyWithWhiteList(member, token) ) {
                forceAuthentication(member);
            }
        }
    }

    filterChain.doFilter(request, response);
}

filterChain.doFilter(request, response)를 추가해주지 않으면 다음 필터로 못넘어갈 수 있으니 유의하자.

 

4. 토큰이 유효한지 확인이 끝났으면 로그인 상태 처리를 위해 forceAuthentication 함수를 실행시켰다.

private void forceAuthentication(Member member) {
    MemberContext memberContext = new MemberContext(member);

    UsernamePasswordAuthenticationToken authentication =
            UsernamePasswordAuthenticationToken.authenticated(
                    memberContext,
                    null,
                    member.getAuthorities()
            );

    //로그인 상태 처리
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(authentication);
    SecurityContextHolder.setContext(context);
}

5. 다시 Security로 돌아와 authenticationEntryPoint는 인증 실패에 대해 커스텀하여 활용하였다.

@Component
@Slf4j
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        RsData rs = RsData.of("F-AccessDenied", "인증 실패", null);

        response.setCharacterEncoding("UTF-8");
        response.setContentType(APPLICATION_JSON_VALUE);
        response.setStatus(403);
        response.getWriter().append(Util.json.toStr(rs));
    }
}