ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • jwt - JwtAuthorizationFilter
    스프링/스프링 시큐리티 2024. 1. 13. 18:08

    저번 포스팅에서 사용자의 요청을 토큰으로 받는 코드를 작성했다.

     

    이번 포스팅에서는 사용자가 개인정보에 접근하기 위해서 다시 로그인을 하는 것이 아니라 JWT 토큰을 이용하여 전자서명을 통해 접근할 수 있도록 하는 코드를 작성할 것이다.

     

    JwtAuthorizationFilter는 어떤 요청이 있을 때 작동하는게 아니라 

     

     

     

    SecurityConfig.class

    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfig {
    
        private final UserRepository userRepository;
        private final CorsConfig corsConfig;
        private final PrincipalDetailsService userDetailsService;
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            ... (생략)
    
            AuthenticationManagerBuilder sharedObject = http.getSharedObject(AuthenticationManagerBuilder.class);
    
            sharedObject.userDetailsService(this.userDetailsService);
            AuthenticationManager authenticationManager = sharedObject.build();
    
            http.authenticationManager(authenticationManager);
    
            http.addFilter(corsConfig.corsFilter());
            http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
            http.addFilter(new JwtAuthorizationFilter(authenticationManager, userRepository));
            ... (생략)
        }
    }

    SecurityCofig에 JwtAuthorizationFilter를 addFilter로 추가해줬다.

     

     

     

     

     

    JwtAuthorizationFilter.class

    public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
        private UserRepository userRepository;
    
        public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
            super(authenticationManager);
            this.userRepository = userRepository;
        }
        // 인증이나 권한이 필요한 주소 요청이 있을 때 해당 필터를 타게 됨.
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("인증이나 권한이 필요한 주소 요청이 되었습니다.");
    
            String header = request.getHeader(JwtProperties.HEADER_STRING);
    
            // 헤더가 없거나 응답한 토큰의 헤더가 아니라면 더 이상 실행안되고 그냥 필터를 타게 함
            if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
                // 헤더가 없거나 응답한 토큰의 헤더가 아니라면 더 이상 실행안되고 그냥 필터를 타게 함
                chain.doFilter(request, response);
                return;
            }
    
            System.out.println("header : " + header);
    
            // Authorization이면 BEARER와 공백을 제거하고 토큰만 추출함
            String token = request.getHeader(JwtProperties.HEADER_STRING)
                    .replace(JwtProperties.TOKEN_PREFIX, "");
    
            // 서명이 정상적으로 진행되면 username을 가져와서 string으로 캐스팅해준다.
            String username = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET)).build().verify(token)
                    .getClaim("username").asString();
    
            // 서명이 정상적으로 되었을 때의 조건문
            if (username != null) {
                Users user = userRepository.findByUsername(username);
    
                // 인증은 토큰 검증시 끝. 인증을 하기 위해서가 아닌 스프링 시큐리티가 수행해주는 권한 처리를 위해
                // 아래와 같이 토큰을 만들어서 Authentication 객체를 강제로 만들고 그걸 세션에 저장
                // 서명이 정상적이니 Authentication 객체를 만들어도 되는 것
                PrincipalDetails principalDetails = new PrincipalDetails(user);
                Authentication authentication = new UsernamePasswordAuthenticationToken(
                        principalDetails, // 나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함.
                        null, // 패스워드는 모르니까 null 처리, 어차피 지금 인증하는게 아니니까
                        principalDetails.getAuthorities());
    
                // 강제로 시큐리티의 세션에 접근하여 값 저장
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
    
            chain.doFilter(request, response);
        }
    }

    토큰을 받아서 인증을 해주는 JwtAuthorizationFilter이다. 위의 주석에 설명을 자세하게 써놨지만 간단히 설명을 해보자면 헤더가 없다면 로직을 수행하지않고 필터를 그냥 돌게 한다. 만약에  서명이 정상적으로 진행이 되었다면, username을 꺼내서 Authentication 객체를 만들고 시큐리티 세션에 접근하여 값을 저장하고 필터를 수행하게 한다.

     

     

     

     

    확인을 위한 api 접근

    @GetMapping("/user")
    public String user(Authentication authentication) {
        PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
        System.out.println("principal의 id : " + principal.getUser().getId());
        System.out.println("principal의 username : " + principal.getUser().getUsername());
        System.out.println("principal의 password : " + principal.getUser().getPassword());
    
        return "user";
    }

    로그인이 완료되었는지 확인을 하기 위해 해당 url에 접근해보면 아래와 같이 출력된다.

     

Designed by Tistory.