[서버개발캠프] JWT 인증 서버와 Spring Cloud Gateway Spring Security 적용해서 연결하기 0편

Sieun Sim·2020년 1월 24일
0

서버개발캠프

목록 보기
11/21

인증 서버와 Gateway의 역할 배분

우선 JWT만을 고려해서 인증 서버와 Gateway의 역할을 나누었다. 크게 보면 결국 토큰 발행은 인증 서버가, parsing&validation은 Gateway가 하게 된다. 지금 당장은 인증 서버가 하는 일이 적어보이긴 한데 후에 Oauth2를 추가했을 때 resource 서버(Google과 같은 third party)에 대한 요청 처리도 인증 서버에서 할 계획이다.

인증 서버

  • 로그인/회원가입 페이지 MVC
  • Refresh Token과 Access Token 발행
  • 로그아웃 블랙리스트 관리
  • Refresh Token db 관리

Gateway

  • 첫 토큰 발행 요청(로그인 요청)을 받으면 인증 서버로 전달해서 Access token과 Refresh Token 받아와서 Client에 전달
  • Client로부터 Refresh Token을 받으면 인증 서버에서 확인하고, 유효하면 새로운 access token을 발급받은 뒤 Client에 전달하고 유효하지 않으면 로그인 하라고 알려주기

SSO(Single Sign On)란?

로그인 한 번으로 여러 서비스를 이용하는 것이다. Google 아이디 가지고 여러 서비스를 사용하는 것이 대표적인 예시인데 Oauth2와 아주 깊게 연관되어 있는 듯 하다. SSO라고 하면 '하나의 로그인으로 여러 서비스 이용하기'라는 컨셉 자체이고 Oauth는 그것을 실행하기 위한 대표적인 기술이라고 생각하면 될 것 같다. 우리 아키텍처에 적용한 MSA에서는 아예 다른 서비스를 사용하지는 않지만 사실은 아예 다른 기능 서버들을 하나의 로그인으로 사용한다는 점에서 내부적인 SSO이다. 다 아는 컨셉을 어려워보이는 키워드로 정리하는 게 이해가 안 될 때도 있었지만 중요한 검색 키워드를 하나 얻는 것만으로도 아주아주 큰 의미가 있다.

기존 Spring Security + MVC 서버에서 Spring Cloud Security로 옮기기

앞서 만들었던 Gateway를 생각하지 않고 만들었던 단일 인증 서버에서는 들어오는 모든 요청에 대해 Request Filter로 필터링을 하고, Controller에 연결해 view page로 띄우는 MVC적 기능들까지 한번에 처리했었다. 하지만 Gateway를 나눈 지금은 직접 Controller를 사용하지 않고 MVC적인 기능을 인증 서버의 역할로 넘겨야 한다.

JWT 파싱과 validate를 Gateway 단에서 하기로 결정했으므로 filter를 사용해 매 요청을 validate 하는 일이 Gateway로 넘어왔다. 이 때 Spring Cloud Security라는 프레임워크가 따로 있어서 이걸 사용해보려고 한다.

문제는 그럼 인증 서버에서는 Spring Security를 사용하지 않을 것이냐..는 건데 애초에 인증 서버에 온 요청은 새로운 JWT 발급에 관한 것들로만 이루어질 것이기 때문에 우선 인증, 인가(권한 관련)에 대해서는 Spring Cloud Security에서 처리할 것이다. SSO의 컨셉에 맞게 Gateway에서 모든 처리를 끝내야지

@EnableWebFluxSecurity와 @EnableWebSecurity의 차이

기존 인증 서버에서 JWT를 검사하기 위한 필터를 http.addFilterBefore()로 입혔고, 이것은 WebSecurityConfigurerAdaptor를 extends해서 configure 메소드를 override해 사용했었다.

@EnableWebSecurity

Add this annotation to an @Configuration class to have the Spring Security configuration defined in any WebSecurityConfigurer or more likely by extending the WebSecurityConfigurerAdapter base class and overriding individual methods:

  • 예시코드

    @Configuration
    @EnableWebSecurity
    public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

          @Override
          public void configure(WebSecurity web) throws Exception {
                  web.ignoring()
                  // Spring Security should completely ignore URLs starting with /resources/
                                  .antMatchers("/resources/**");
          }
    
          @Override
          protected void configure(HttpSecurity http) throws Exception {
                  http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest()
                                  .hasRole("USER").and()
                                  // Possibly more configuration ...
                                  .formLogin() // enable form based log in
                                  // set permitAll for all URLs associated with Form Login
                                  .permitAll();
          }
    
          @Override
          protected void configure(AuthenticationManagerBuilder auth) {
                  auth
                  // enable in memory based authentication with a user named "user" and "admin"
                  .inMemoryAuthentication().withUser("user").password("password").roles("USER")
                                  .and().withUser("admin").password("password").roles("USER", "ADMIN");
          }
    
          // Possibly more overridden methods ...

    }

@EnableWebFluxSecurity

Add this annotation to a Configuration class to have Spring Security WebFlux support added. User's can then create one or more ServerHttpSecurity Bean instances. A minimal configuration can be found below:

  • 예시코드

    @EnableWebFluxSecurity
    public class MyExplicitSecurityConfiguration {
    // @formatter:off
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http
    .authorizeExchange()
    .anyExchange().authenticated()
    .and()
    .httpBasic().and()
    .formLogin();
    return http.build();
    }
    // @formatter:on

       // @formatter:off
       @Bean
       public MapReactiveUserDetailsService userDetailsService() {
            UserDetails user = User.withDefaultPasswordEncoder()
                 .username("user")
                 .password("password")
                 .roles("USER")
                 .build();
            return new MapReactiveUserDetailsService(user);
       }
       // @formatter:on

    }

기존과 비슷한 설정을 할 수 있다. 차이가 있다면 @EnableWebSecurity는 configure 메소드 자체를 override 해서 ServerHttpSecurity 객체를 사용할 수 있게 해줬다면, @EnableWebFluxSecuritySecurityWebFilterChain를 빈으로 등록해 ServerHttpSecurity 객체를 커스터마이징 할 수 있게 해 준다. 원래 있는것에 override 해서 사용하냐, 빈으로 등록해서 빌드하느냐의 차이가 있지만 전과 동일하게 ServerHttpSecurity 객체를 관리할 수 있어서 다행이다.

내 계획은 @EnableWebFluxSecurity 를 사용해 ServerHttpSecurity 객체에 JWT를 검사하는 filter를 넣는 것이다. 모든 요청에 대해 공통적으로 수행할 기능을 넣을 때 filter는 유용한 것 같다. 다른 방식은 지금은 DIY로 수작업 하는것 밖에는 생각나지 않는다.

기존 인증 서버에서 Parsing&Validate 부분 Gateway로 옮기기

먼저 spring security를 Gateway로 옮기기 위해 Gateway 프로젝트에 디펜던시를 추가해준다. pom.xml에

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

추가해주었다. 이제 @EnableWebFluxSecurity@SecurityWebFilterChain를 사용할 수 있다.

Filter Chain에 JWTRequestFilter를 만들어 넣어줄 것이다. 깊게 알아갈수록 이전 단일 인증서버를 만들 때 얼마나 멋모르고 짜집기했는지 깨닫는 중..

참고자료

EnableWebSecurity (Spring Security 4.0.4.RELEASE API)

EnableWebFluxSecurity

2개의 댓글

comment-user-thumbnail
2020년 9월 21일

개인적으로 프로젝트 하다가 개발캠프 분을 만나니 반갑네요 ㅎㅎ 잘보고 갑니다.

답글 달기
comment-user-thumbnail
2020년 11월 9일

인증 서버 = Redis 인건가요 ??

답글 달기