Spring Security와 Jwt를 이용해서 인증과 인가를 구현했습니다.
https://khjoon372.tistory.com/262
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SpringSecurityConfig {
private final CustomAccessDeniedHandler accessDeniedHandler;
private final CustomAuthenticationEntryPoint authenticationEntryPoint;
private final JwtAuthenticationExceptionHandler exceptionFilter;
private final RedisService redisService;
private final TokenProvider tokenProvider;
@Value("${jwt.token.secret}")
private String secretKey;
private final UrlBasedCorsConfigurationSource corsConfigurationSource;
@Bean
public WebSecurityCustomizer webSecurityCustomizer(){
return (web) -> web.ignoring()
.antMatchers(
"/favicon.ico",
"/health",
"/",
"/swagger-ui.html",
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/api-docs/**",
"/api/members/login"
);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic().disable()//토큰 인증 방식으로 하기 위해서 HTTP 기본 인증 비활성화
.csrf().disable()//CSRF 공격 방어 기능 비활성화
.cors()
.configurationSource(corsConfigurationSource)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()//모든 접근 허용
//.antMatchers(HttpMethod.POST, "/api/members/jwt/test").authenticated()//인증 필요로 접근 막기
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.addFilterBefore(new JwtFilter(tokenProvider, redisService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(exceptionFilter, JwtFilter.class)
.build();
}
}
사용자 정보와 email, 역할을 이용해서 jwt를 생성한다.
public String createAccessToken(Long memberId, String memberRole, String email, Collection<? extends GrantedAuthority> authorities) {
//30분
long now = (new Date()).getTime();
Date validity = new Date(now + this.accessTokenValidityInMilliseconds);
//토큰 생성
return Jwts.builder()
.setSubject(String.valueOf(memberId))
.claim(AUTHORITIES_KEY, authorities)
.claim("memberRole", memberRole)
.claim("email", email)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
private final RedisService redisService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest httpServletRequest = request;
String jwt = tokenProvider.resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)){
// jwt는 정상적인 형태이나, 로그아웃 한 토큰인가?
if(!redisService.validateLoginToken(jwt)) {
logger.error("이미 로그아웃 된 토큰 발견");
throw new JwtHandler(ErrorStatus.JWT_BAD_REQUEST);
}
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}else{
throw new JwtHandler(ErrorStatus.JWT_TOKEN_NOT_FOUND);
}
filterChain.doFilter(httpServletRequest, response);
}
}
문제 상황: jwt 토큰 발급 후 jwt filter를 통과하면서 회원 Id를 가져오기 위해서 jwt 토큰을 파싱해서 SpringContextHolder의 하위 객체인Authentication에 값이 담겨야 하는데 자꾸 null값이 담기는 상황이다...
어디서 문제가 발생했는지 모르는 상황에선 전체적인 흐름을 알아야 해서 디버깅하면서 공부도 많이 된 것 같다.