JWT 도입 2편 - Access Token 검증

민선규·2024년 7월 17일
0

JAVA

목록 보기
20/25
post-thumbnail

JWT

지난 편에서는 Access Token 발급을 구현하였고 포스트맨으로 Access Token 값이 정상적으로 반환되는 것을 확인할 수 있었습니다. 이번에는 발급 받은 Access Token을 활용하여 인증을 처리하는 로직을 구현하고 테스트를 해보겠습니다.

MemberSecurity

MemberSecurity는 CustomUserDetails에 포함되어 있는 클래스로 회원 정보가 담겨져 있습니다.

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberSecurity {
    private String uuid;
    private String role;
}

CustomUserDetails

다음으로는 CustomUserDetails입니다. CustomUserDetails은 UserDetails을 상속 받고 있으며 스프링 시큐리티 인증 토큰을 생성할 때 필요한 클래스입니다. 내부에는 MemberSecurity를 가지고 있습니다.

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

    private final MemberSecurity memberSecurity;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return memberSecurity.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return memberSecurity.getUuid();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

JwtFilter

JWT 토큰을 검증하기 위해서는 커스텀 필터가 필요합니다. 커스텀 필터는 OncePerRequestFilter를 상속받고 있는데 여기서 OncePerRequestFilter는 하나의 HTTP 요청당 필터가 단 한 번만 실행되도록 보장합니다. 이는 필터가 여러 번 실행되는 것을 방지하고, 효율성을 높이며, 불필요한 처리와 성능 저하를 방지합니다.

각 로직의 설명은 코드에 주석으로 작성하였습니다.

@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    	//Authorization 이름을 가진 헤더의 값을 가져옵니다.
        String accessToken= request.getHeader("Authorization");
        //가져온 값이 null 이거나 Bearer로 시작하지 않으면 다음 필터로 이동합니다.
        if (accessToken == null || !accessToken.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

		//Access Token 값을 가져옵니다.
        String token = accessToken.split(" ")[1];
		//가져온 Access Token이 만료되었는지 확인하고 만료되었다면 에러 response를 설정하고 리턴합니다.
        try {
            jwtUtil.isExpired(token);
        }
        catch (ExpiredJwtException e){
            PrintWriter writer = response.getWriter();
            writer.write("Access Token Expired");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

		//Access Token의 category 값을 가져옵니다.
        String category = jwtUtil.getCategory(token);
        //category가 access가 아닌 경우에 에러 response를 설정하고 리턴합니다.
        if(!category.equals("access")){
            PrintWriter writer = response.getWriter();
            writer.write("Invalid Access Token");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

		//Access Token의 uuid와 role 값을 가져옵니다.
        String uuid = jwtUtil.getUuid(token);
        String role = jwtUtil.getRole(token);

		//가져온 값으로 MemberSecurity을 생성합니다.
        MemberSecurity memberSecurity = MemberSecurity.builder()
                        .uuid(uuid)
                        .role(role)
                        .build();

		//memberSecurity으로 CustomUserDetails을 생성합니다.
        CustomUserDetails customUserDetails = new CustomUserDetails(memberSecurity);
        //스프링 시큐리티 인증 토큰을 생성합니다.
        Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
        //세션에 사용자 등록을 진행합니다.
        SecurityContextHolder.getContext().setAuthentication(authToken);
		//다음 필터로 이동합니다.
        filterChain.doFilter(request, response);
    }
}

TestController

TestController는 인증이 잘 되는지 확인하는 컨트롤러입니다. /test url로 GET 요청을 했을 때 Test Success가 반환이 되면 성공적으로 인증 처리가 된 것입니다.

@RestController
public class TestController {
    
    @GetMapping("/test")
    public String test(){
        return "Test Success";
    }
}

SecurityConfig

위에서 작성한 JwtFilter를 필터에 추가하였습니다. 또한 /test url로 GET 요청을 할 때는 인증이 필요하다는 설정을 해주었습니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

	private final JwtUtil jwtUtil;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
        //JWTFilter 추가
        //JwtFilter를 UsernamePasswordAuthenticationFilter 전에 설정하는 것은 JwtFilter가 JWT 인증을
        //처리하고, 인증 정보를 SecurityContext에 설정한 후, 이후 필터들이 이 정보를 활용할 수 있게 하기 위해서 입니다.
        http
                .addFilterBefore(new JwtFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
               
        //경로별 인가 작업
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers(HttpMethod.GET,"/test").authenticated()
                        .anyRequest().permitAll());
                        
    }
}

TEST

첫 번째 케이스는 Access Token을 포함하지 않고 요청을 했을 때 입니다. 결과를 보면 Invalid Access Token와 401 상태 코드를 확인할 수 있습니다.

두 번째 케이스는 Access Token을 포함하고 요청을 했을 때 입니다. 결과를 보면 Test Success와 200 상태 코드를 확인할 수 있습니다.

이렇게 Access Token을 발급 받고 인증을 처리하는 필터까지 구현하였습니다. 다음 포스트에서는 Refresh Token를 발급받는 로직을 작성해보겠습니다!

0개의 댓글

관련 채용 정보