프로젝트를 하면서 JWT를 통해 사용자 인증, 인가를 구현하게 되었는데
/
package ourtine.auth.filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import ourtine.auth.entity.UserDetailsImpl;
import ourtine.auth.service.UserDetailsServiceImpl;
import ourtine.auth.utils.JwtUtil;
import java.io.IOException;
@Slf4j(topic = "JWT 검증 및 인가")
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
log.info("JWT 토큰 추출 및 Authentication 객체 생성 및 반환");
String url = req.getRequestURI();
log.info("url = " + url);
if(StringUtils.hasText(url) && url.startsWith("/api/auth/kakao") || url.startsWith("/api/auth/apple")){
filterChain.doFilter(req,res);
return;
}
// 쿠키에서 JWT 토큰 빼오기
String tokenValue = jwtUtil.getTokenFromRequest(req);
log.info("tokenValue = " + tokenValue);
// 토큰 존재 시, Authentication 객체 생성하여 인가 완료
if (StringUtils.hasText(tokenValue)) {
tokenValue = jwtUtil.substringToken(tokenValue);
log.info(tokenValue);
if (!jwtUtil.validateToken(tokenValue)) {
log.error("Token Error");
return;
}
log.info("Token Validation 완료");
// Authentication 객체 생성 ( By Username )
// ( SecurityContextHolder -> SecurityContext -> Authentication 객체 )
// Authentication 객체 <- UsernamePasswordAuthenticationToken, OAuth2AuthenticationToken
String username = jwtUtil.getUsername(tokenValue);
try {
log.info("username = " + username);
log.info("setAuthentication");
setAuthentication(username);
} catch (Exception e) {
log.error(e.getMessage());
return;
}
}
log.info("chain.doFilter() 호출");
// JWT 토큰이 존재하지 않으면, 필터 통과!
filterChain.doFilter(req, res);
}
// 인증 처리
public void setAuthentication(String username) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = createAuthentication(username);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
// 인증 객체 생성 ( UserDetails - UsernameAuthentication )
// ( PrincipalDetails - OAuth2Authentication )
private Authentication createAuthentication(String username) {
UserDetailsImpl userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
이와 같이 코드를 사용했었다.
원래 내가 원하는 것은, 로그인 URL에 들어올때만 이 필터를 거치게 하고 싶었기에
Spring Security의 설정 파일인 WebSecurityConfig 파일에 다음과 같이 코드를 작성해놨었다.
package ourtine.auth.config.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ourtine.auth.entity.UserRoleEnum;
import ourtine.auth.filter.JwtAuthorizationFilter;
import ourtine.auth.service.UserDetailsServiceImpl;
import ourtine.auth.utils.JwtUtil;
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity(debug = true) // Spring Security 지원을 가능하게 함, 디버깅 모드 활성화 : 어떤 필터를 타는지 로그를 찍어줌.
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured Annotation 활성화.
public class WebSecurityConfig {
// JWT Provider
private final JwtUtil jwtUtil;
private final JwtAuthorizationFilter jwtAuthorizationFilter;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// // Spring Filter 등록 ( JwtAuthorizationFilter )
// @Bean
// public JwtAuthorizationFilter jwtAuthorizationFilter() {
// return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
// }
// FilterChain을 통해 인증, 인가 관리
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable()
.cors().disable()
.formLogin().disable()
.logout().disable()
.httpBasic().disable()
.rememberMe().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/user/**").authenticated();
// .antMatchers("/api/user/**").hasRole(UserRoleEnum.USER.toString())
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// .antMatchers("/csrf-token").permitAll()
// .antMatchers(HttpMethod.POST, "/authorize", "authorize/refresh", "/users").anonymous()
// .antMatchers(HttpMethod.POST, "/oauth/unlink").authenticated()
// .antMatchers("/oauth/**").permitAll()
// .anyRequest().authenticated();
http.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
FilterChain을 커스터마이징 해서 @Bean으로 등록하는 메서드를 보면
/api/user 하위 URL에 대한 요청에만 인증을 하고,
/api/auth 하위 URL에 대한 요청은 JWTAuthorizationFilter 가 통과되지 않도록 설정한 것을 볼 수 있었다.
하지만, JWTAuthorzationFilter 에 @Component로 Bean으로 등록 처리해주니,
URL 패턴을 지정할 수 없고, 모든 URL 패턴에 적용된다는 것을 알게 되었다!
( = @WebFilter 어노테이션을 이용하면, 특정 URL 패턴에 매핑되는 필터를 추가할 수 있다고 한다 )
@Component 을 제거하고, 한번 돌려보고 결과를 기록해봐야겠다