build.gradle
JWT Token을 검증하기 위해선 Spring Security를 사용해야한다.
1
implementation 'org.springframework.boot:spring-boot-starter-security'
Spring Filter
Filter는 Spring DispatcherServlet에 요청이 들어오기 전 동작하는 자바 서블릿에서 제공해주는 기능이다.
이 기능을 사용하면 Spring에 요청값이 들어오기 전 Jwt 토큰 검사를 할 수 있다.
Filter는 아래와 같은 용도 사용된다.
- 공통된 보안 및 인증/인가 관련 작업
- 모든 요청에 대한 로깅 또는 감사
- 이미지/데이터 압축 및 문자열 인코디
- Spring과 분리되어야 하는 기능
코드
JwtTokenProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
public class JwtTokenProvider {
private final Key key;
.... 이전 코드
// 토큰 만료여부 체크
public boolean extractSubject(String accessToken) {
Claims claims = parseClaims(accessToken);
return !claims.getExpiration().before(new Date());
}
private Claims parseClaims(String accessToken) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(accessToken)
.getBody();
}
// Request의 Header에서 token 값을 가져옵니다. "Authorization" : "TOKEN값'
public String resolveToken(HttpServletRequest request) {
return request.getHeader("Authorization");
}
// 토큰에서 회원 정보 추출
public String getUserPk(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject();
}
// JWT 토큰에서 인증 정보 조회
public Authentication getAuthentication(String token) {
/*
* UserDetails 가져오는 비지니스 로직 추가되야함
* */
return new UsernamePasswordAuthenticationToken(this.getUserPk(token), "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
JwtAuthenticationFilter
Token인증이 필요한 API 메소드 호출 이전 Header에 저장되어있는 토큰을 가져와 유효여부를 체크한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 헤더에서 JWT 를 받아옵니다.
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 토큰인지 확인합니다.
if (token != null && jwtTokenProvider.extractSubject(token)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옵니다.
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장합니다.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (ExpiredJwtException e) {
//토큰 만료 에러처리
} catch (JwtException | IllegalArgumentException e) {
//Jwt 에러처리
} catch (SignatureException e) {
//Signature 불일치 에러처리
}
filterChain.doFilter(request, response);
}
}
SecurityConfig
Config에서 위에 코딩한 Filter를 설정해주고 인증이 필요한 Url을 지정해준다.
아래와 같이 지정하면 /auth/**(token 발급 API는 token인증이 필요 없음)을 제외한 url은 Header에 들어간 token인증을 하게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.httpBasic(HttpBasicConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
new AntPathRequestMatcher("/auth/**") // auth url로 시작하는 API는 JwtAuthenticationFilter를 타지않음
)
.permitAll()
.requestMatchers(
new AntPathRequestMatcher("/**") // auth를 제외한 모든 url은 JwtAuthenticationFilter를 거침
)
.authenticated()
.anyRequest().denyAll()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
테스트
JWT 이란? POST 하단에 테스트 참고