내일배움캠프 28일차 TIL : Spring - Filter, Spring Security

김원기·2024년 5월 31일

TIL

목록 보기
32/42
post-thumbnail

내일배움캠프 28일차 TIL : Spring - Filter, Spring Security

https://github.com/WonGi-Kim/spring_personal_project1

작성한 코드를 기반으로 설명하도록 하겠다.

JwtAuthorizationFilter 클래스

@Slf4j
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    public JwtAuthorizationFilter(UserRepository userRepository, JwtUtil jwtUtil, UserDetailsService userDetailsService) {
        this.userRepository = userRepository;
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }
}

@Slf4j 어노테이션은 Lombok을 사용하여 로그 객체를 자동으로 생성합니다.

public class JwtAuthorizationFilter extends OncePerRequestFilter

JwtAuthorizationFilter 클래스는 OncePerRequestFilter를 확장하여 각 요청마다 한 번만 실행되는 필터를 구현한다.

doFilterInternal 메서드

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
    String tokenValue = jwtUtil.getJwtFromHeader(req);
    if (tokenValue != null) {
        try {
            jwtUtil.validateToken(tokenValue);
            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
            setAuthentication(info.getSubject());

            // 로그 추가: 토큰 정보 확인
            log.info("Decoded token: {}", tokenValue);
        } catch (JwtException e) {
            log.error(e.getMessage());
        }
    }
    filterChain.doFilter(req, res);
}

doFilterInternal 메서드는 HTTP 요청과 응답을 처리한다.

jwtUtil.getJwtFromHeader(req)를 호출하여 요청 헤더에서 JWT 토큰을 추출한다.

토큰이 존재하면, jwtUtil.validateToken(tokenValue)로 토큰의 유효성을 검사한다.

jwtUtil.getUserInfoFromToken(tokenValue)를 사용하여 토큰에서 사용자 정보를 추출한다.

setAuthentication(info.getSubject())를 호출하여 인증 정보를 설정한다.

filterChain.doFilter(req, res)를 호출하여 다음 필터로 요청을 전달한다.

setAuthentication

private void setAuthentication(String username) {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    Authentication authentication = createAuthentication(username);
    context.setAuthentication(authentication);

    SecurityContextHolder.setContext(context);
}

setAuthentication 메서드는 주어진 사용자 이름을 기반으로 인증을 설정합니다.

SecurityContextHolder.createEmptyContext()로 빈 보안 컨텍스트를 생성합니다.

createAuthentication(username)를 호출하여 인증 객체를 생성합니다.

context.setAuthentication(authentication)로 컨텍스트에 인증 객체를 설정합니다.

SecurityContextHolder.setContext(context)로 현재 스레드에 보안 컨텍스트를 설정합니다.

createAuthentication

private Authentication createAuthentication(String username) {
    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}

createAuthentication 메서드는 사용자 이름을 기반으로 인증 객체를 생성합니다.

userDetailsService.loadUserByUsername(username)로

UserDetails 객체를 로드합니다.
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())로 UsernamePasswordAuthenticationToken 객체를 생성합니다. 이는 사용자의 인증 정보를 나타냅니다.

WebSecurityConfig 클래스

public class WebSecurityConfig {
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // CSRF 설정
    http.csrf((csrf) -> csrf.disable());

    http.authorizeHttpRequests((authorizeHttpRequests) ->
        authorizeHttpRequests
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            .requestMatchers("/api/todos/**").permitAll()
            .requestMatchers("/api/user/login").permitAll()
            .requestMatchers("/api/user/signup").permitAll()
            .anyRequest().authenticated()
    );
    JwtAuthorizationFilter jwtFilter = new JwtAuthorizationFilter(userRepository, jwtUtil, userDetailsService);
    http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

@Bean 어노테이션은 이 메서드가 Spring 컨텍스트에 SecurityFilterChain 빈으로 등록된다는 것을 나타냅니다.

HttpSecurity 객체를 사용하여 보안 설정을 구성합니다.

http.csrf((csrf) -> csrf.disable())로 CSRF 보호를 비활성화합니다.

http.authorizeHttpRequests를 사용하여 URL 요청 패턴에 대한 권한 설정을 구성합니다:

  • 정적 자원에 대한 요청을 허용 (permitAll).
  • /api/todos/**, /api/user/login, /api/user/signup 경로에 대한 요청을 허용 (permitAll).
  • 기타 모든 요청은 인증 필요 (authenticated).

JwtAuthorizationFilter 객체를 생성하고, UsernamePasswordAuthenticationFilter 뒤에 추가합니다.

http.build()를 호출하여 보안 필터 체인을 빌드하고 반환합니다.

작동 순서

  1. HTTP 요청 수신: 클라이언트가 서버로 HTTP 요청을 보낸다.

  2. Spring Security Filter Chain: Spring Security는 요청을 받으면 필터 체인을 통해 요청을 처리한다.

  3. CSRF 비활성화: WebSecurityConfig 클래스의 securityFilterChain 메서드에서 CSRF 보호가 비활성화 된다.

  4. HTTP 요청 권한 설정: 요청 패턴에 따라 권한이 설정됩니다.

http.authorizeHttpRequests((authorizeHttpRequests) ->
    authorizeHttpRequests
        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
        .requestMatchers("/api/todos/**").permitAll()
        .requestMatchers("/api/user/login").permitAll()
        .requestMatchers("/api/user/signup").permitAll()
        .anyRequest().authenticated()
);
  1. JWT Authorization Filter 추가: JwtAuthorizationFilter가 필터 체인에 추가된다.
JwtAuthorizationFilter jwtFilter = new JwtAuthorizationFilter(userRepository, jwtUtil, userDetailsService);
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
  1. JWT Authorization Filter 동작: JwtAuthorizationFilter가 실행됩니다. 필터는 OncePerRequestFilter를 상속받아 각 요청마다 한 번 실행됩니다.
  • 헤더에서 JWT 추출: doFilterInternal 메서드에서 jwtUtil.getJwtFromHeader(req)를 호출하여 JWT 토큰을 추출합니다.
String tokenValue = jwtUtil.getJwtFromHeader(req);
  • 토큰 유효성 검사: 토큰이 존재하면 jwtUtil.validateToken(tokenValue)로 유효성을 검사합니다.
if (tokenValue != null) {
    try {
        jwtUtil.validateToken(tokenValue);
        Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
        setAuthentication(info.getSubject());
        log.info("Decoded token: {}", tokenValue);
    } catch (JwtException e) {
        log.error(e.getMessage());
    }
}
  • 사용자 정보 추출 및 인증 설정: 토큰이 유효하면 jwtUtil.getUserInfoFromToken(tokenValue)를 호출하여 사용자 정보를 추출하고 setAuthentication(info.getSubject())를 통해 인증 정보를 설정합니다.
Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
setAuthentication(info.getSubject());
  • 로그 기록 및 예외 처리: 디코딩된 토큰 정보를 로그에 기록하고, 예외 발생 시 에러 메시지를 로그에 기록합니다.
log.info("Decoded token: {}", tokenValue);
} catch (JwtException e) {
    log.error(e.getMessage());
}
  1. 다음 필터로 요청 전달: 필터 체인의 다음 필터로 요청이 전달됩니다.
filterChain.doFilter(req, res);
  1. SecurityContextHolder에 인증 설정: setAuthentication 메서드에서 SecurityContextHolder에 인증 정보를 설정합니다.
private void setAuthentication(String username) {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    Authentication authentication = createAuthentication(username);
    context.setAuthentication(authentication);
    SecurityContextHolder.setContext(context);
}
  1. UserDetailsService를 통한 사용자 로드: createAuthentication 메서드에서 UserDetailsService를 사용하여 사용자 세부 정보를 로드하고, UsernamePasswordAuthenticationToken을 생성합니다.
private Authentication createAuthentication(String username) {
    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
  1. 인증된 요청 처리: 인증된 요청은 필요한 권한을 확인하고, 해당 권한이 있으면 요청을 처리합니다. 그렇지 않으면 접근이 거부됩니다.
profile
혼자 공부하는 블로그라 부족함이 많아요 https://www.notion.so/18067a27ac7e4f4790dde645fb3bf3d3?pvs=4

0개의 댓글