BE/Spring

[Spring Security] JWT - 2. JWT 필터 설정 및 필터 우선순위 적용 방법

멍목 2022. 6. 11. 15:38
반응형

이 포스팅은 아래의 강의를 참고하였으니 여기에서 공부하시는 것을 추천드립니다.

https://inf.run/tcLk

 

[무료] 스프링부트 시큐리티 & JWT 강의 - 인프런 | 강의

스프링부트 시큐리티에 대한 개념이 잡힙니다., - 강의 소개 | 인프런...

www.inflearn.com


1. JWT(JSON Web Token)

  • 정보를 JSON 객체로 안전하게 전송하기 위한 개방형 표준(RFC 7519)
  • 디지털 서명이 되어 있으므로 신뢰 가능
  • RSA or ECDSA 알고리즘을 이용
  • 구조
    • Header: 토큰 유형과 서명 알고리즘 정보가 담김
    • Payload: 엔터티(사용자) 및 추가데이터에 대한 설명. 즉, 통신하고자하는 정보가 담김
    • Signature: 서명관련된 정보가 담김
  • Signature: ((Header + Payload)를 HS256으로 암호화)를 BASE64로 암호화
  • JWT: BASE64(Header).BASE64(Payload).BASE64(HS256(Header+Payload))

JWT(.으로 Header, Payload, Signature를 구분)

 

2. JWT 사용하기 위한 Spring 설정

1) pom.xml에 아래의 의존성 추가

<!-- jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.2</version>
</dependency>

 

 

2) CorsConfig 자바 파일 추가

package com.cos.security1.config;

import org.springframework.web.filter.CorsFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);       // 내 서버가 응답할 때 JSON을 js에서 처리할 수 있게 할 지 설정
        config.addAllowedOrigin("*");           // 모든 ip의 응답을 허용
        config.addAllowedHeader("*");           // 모든 geader에 응답을 허용
        config.addAllowedMethod("*");           // 모든 Method(post, get, put, delete, patch) 요청 허용
        source.registerCorsConfiguration("/api/**", config);

        return new CorsFilter(source);
    }
}

 

3) filter 패키지 생성 후 filterConfig 파일 추가

package com.cos.security1.config.filter;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<MyFilter1> filter1(){
        FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>(new MyFilter1());
        bean.addUrlPatterns("/*");         // 모든 요청에 대해서 적용
        bean.setOrder(1);                   // 가장 먼저 실행되도록(낮은 번호가 필터중에서 가장 먼저 실행됨)

        return bean;
    }

    @Bean
    public FilterRegistrationBean<MyFilter2> filter2(){
        FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2());
        bean.addUrlPatterns("/*");
        bean.setOrder(0);

        return bean;
    }
}

 

4) filter 패키지에 커스텀 필터 파일 추가(테스트용)

필터별로 우선순위가 언제 적용되는 지 확인하기 위하여 아래의 필터들을 만든다.

 

MyFilter1.java

package com.cos.security1.config.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter1");
        chain.doFilter(request, response);
    }
}

 

MyFilter2.java

package com.cos.security1.config.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter2 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter2");
        chain.doFilter(request, response);
    }
}

 

MyFilter3.java

package com.cos.security1.config.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter3 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter3");
        chain.doFilter(request, response);
    }
}

 

5) SecurityConfig 파일에서 아래와 같이 수정

Spring Security 기본 로그인 및 Oauth2 설정 부분 주석처리 필요(강의에서 JWT와 설정이 다르다고함)

package com.cos.security1.config;

import com.cos.security1.config.filter.MyFilter1;
import com.cos.security1.config.filter.MyFilter3;
import com.cos.security1.config.oauth2.PrincipalOauth2UserSerivce;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.web.filter.CorsFilter;


// 구글 로그인 과정
// 1. 코드받기(인증) 2. 액세스토큰(권한) 3. 사용자 프로필정보 가져오기 4. 그 정보를 토대로 회원가입 진행

@Configuration
@EnableWebSecurity      // spring security filter가 Spring filter chain 에 등록
// securedEnabled: @Secured 어노테이션 활성화. @Secured? 특정 URL에 대해서만 간단하게 권한 처리를 할 수 있는 어노테이션
// prePostEnabled: @PreAuthorize, @PostAuthorize 어노테이션 활성화.
// @PreAuthorize: 해당 메소드 진입 전 처리. @PostAuthorize: 해당 메소드 진입 후 처리
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PrincipalOauth2UserSerivce principalOauth2UserSerivce;

    @Autowired
    private final CorsFilter corsFilter;

    /**
     * password 암호화
     * @return
     */
    @Bean
    public BCryptPasswordEncoder encodePw(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception{       // Spring Security 설정
        http.csrf().disable();      // csrf 비활성화

        /********* JWT 관련 설정 부분(시작) ******/
        // Spring Security 기본 로그인 및 OAuth2를 사용하기 위해서는 아래의 설정 주석 처리 필요
        ///*

        http.addFilterBefore(new MyFilter3(), BasicAuthenticationFilter.class);     // BasicAuthenticationFilter 이전에 MyFilter3을 추가하겠다.
        // 시큐리티 필터가 커스텀 필터보다 먼저 실행됨

        // 커스텀 필터를 시큐리티 필터보다 먼저 실행시키는 방법
        // 시큐리티 필터 중, 가장 먼저 실행되는 필터는 SecurityContextPersistenceFilter 이므로 이것보다 앞에 위치하면 됨.0
        //http.addFilterBefore(new MyFilter1(), SecurityContextPersistenceFilter.class);

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)         // 세션을 사용하지 않겠다라는 의미
                        .and()
                        .addFilter(corsFilter)              // 인증이 있어야할 때 시큐리티 필터에 등록. (인증이 필요없을 때 해당 컨트롤러에 @CrossOrigin 어노테이션을 사용하면 됨)
                        .formLogin().disable()              // Spring Security 로그인 사용 X
                        .httpBasic().disable()              // http를 제외한 다른 방식(js로 요청 등)을 허용하겠다.
                        .authorizeRequests()
                        .antMatchers("/api/v1/user/**")
                        .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                        .antMatchers("/api/v1/manager/**")
                        .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                        .antMatchers("/api/v1/admin/**")
                        .access("hasRole('ROLE_ADMIN')")
                        .anyRequest().permitAll();
        //*/
        /********* JWT 관련 설정 부분(끝) ******/

        /********* Spring Security 기본 로그인 및 Oauth2 설정 부분(시작) ******/
        // JWT를 사용하기 위해서는 아래의 설정 주석 처리 필요

        /*
        // URL에 따른 접근 제한 처리
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()        // URL user : 인증이 되어야 함
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")      // URL manager : 권한 'ROLE_ADMIN', 'ROLE_MANAGER'가 있어야함
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")         // URL admin : 권한 'ROLE_ADMIN'이 있어야함
                .anyRequest().permitAll()      // 위에 명시되지 않은 URL 은 로그인 및 권한 검사 X
                .and()
                .formLogin()
                .loginPage("/loginForm")       // formLogin이 필요한 경우, /loginForm 으로 보낸다.
                .loginProcessingUrl("/login")   // login 주소가 호출이 되면 시큐리티가 낚이채서 대신 로그인을 진행
                .defaultSuccessUrl("/")        // login 성공 시, 보내줄 기본 url
                // OAuth2 로그인 설정(시작)
                .and()
                .oauth2Login()
                .loginPage("/loginForm")    // OAuth2의 로그인 페이지 URL.
                .userInfoEndpoint()
                .userService(principalOauth2UserSerivce)// 구글 로그인이 완료된 이후에 후 처리가 필요. (코드가 아닌 액세스토큰+사용자프로필의정보)를 가져옴
                // OAuth2 로그인 설정(끝)
        ;
        */
        /********* Spring Security 기본 로그인 및 Oauth2 설정 부분(끝) ******/
    }
}

 

 

3. 필터 우선순위 확인

Controller에 만들어둔 매핑된 아무 주소로 접속 시 아래처럼 나타난다.

이유:

filter3: SecurityConfig 에서 BasicAuthenticationFilter 이전에 MyFilter3을 추가했기 때문에 커스텀 필터 중 가장 먼저 나왔다.

filter1 & filter2: 스프링시큐리티 기본 필터가 끝난 이후 나오며, FilterConfig에서 order에 맞게 우선순위가 나온다.

(우선순위는 숫자가 낮을수록 먼저 높은 우선순위를 가짐)

필터이름 우선순위
filter1 1
filter2 0

 

반응형