WEB/Security

OAuth 2.0 Resource Server

Tony Lim 2022. 12. 19. 16:42
728x90

OAuth2.0 인가 프레임워크의 역할중 클라이언트 및 인가서버와의 통신을 담당하는 리소스 서버의 기능을 필터 기반으로 구현한 모듈

설정만으로 클라이언트 리소스 접근 제한, 토큰 검증을 위한 인가서버와의 통신 등의 구현이 가능하다

앱의 권한관리를 별도 인가서버에 위임하는 경우에 사용할 수 있으며 , 리소스 서버는 요청을 인가할 때 이 인가 서버에 물어볼 수 있다.

 

OAuth2ResourceServer

클라이언트의 접근을 제한 하는 인가 정책을 설정한다.

인가서버에서 발급한 Access Token(JWT, Opaque)의 유효성을 검증하고 접근 범위에 따라 적절한 자원을 전달하도록 설정한다.


Resource Server 시작하기 - OAuth2ResourceServerProperties

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/certs
          issuer-uri: http://localhost:8080/realms/oauth2

jwk-set-uri = 인가 서버가 발급한 jwt 토큰의 공개키 정보를 검색할 수 있는 엔드포인트를 나타낸다.

기본적으로 spring security dependency만 가져가면 랜덤한 값으로 이뤄진 password를 주고 로그인창을 띄우게된다.

application.yaml에 resource server관련된 설정을 하면 로그인창을 띄우지는 않지만 다른 url로 접근하려 하면 401 unAuthorized 가 뜨게 된다.
auth server에게 token 을 얻어와서 해당 token과 함께 다른 url로 접근이 가능해지게 된 것이다.
즉 resource server 답게 token 을 들고와야지만 resource를 허락해주겠다는것이다.


Authentication EntryPoint

어떤 조건이 성립해야 어떤 authentication entrypoint로 가는지 알고 있어야 한다.

https://tonylim.tistory.com/389

 

Spring Security Fundamentals

SecurityBuilder는 인터페이스이고 build메소드를 호출하게 되면 init configue메소드를 내부적으로 또 호출하게 된다. public final class HttpSecurity extends AbstractConfiguredSecurityBuilder implements SecurityBuilder, HttpSecu

tonylim.tistory.com

현재 application.yml에 resource server설정이 되어있음으로 
authentication Entrypoint중에 BearerTokenAuthenticationEntryPoint 가 OAuth2ResourceServerConfigurer에서 기본으로 지정되어있고 ExceptionTranslationFilter에 적용이 된다.


자동설정에 의한 초기화 과정

@AutoConfiguration(before = { SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class })
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnClass(BearerTokenAuthenticationToken.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ Oauth2ResourceServerConfiguration.JwtConfiguration.class,
      Oauth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
public class OAuth2ResourceServerAutoConfiguration {

}
class Oauth2ResourceServerConfiguration {

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(JwtDecoder.class)
   @Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
         OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration.class })
   static class JwtConfiguration {

   }

   @Configuration(proxyBeanMethods = false)
   @Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
         OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2SecurityFilterChainConfiguration.class })
   static class OpaqueTokenConfiguration {

   }
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class OAuth2SecurityFilterChainConfiguration {

   @Bean
   @ConditionalOnBean(JwtDecoder.class)
   SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
      http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
      http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
      return http.build();
   }

}

기본적인 SecurityFilter Chain이 필요하다. 나중에 WebSecurity에서 FilterProxyChain을 만들때 필요하기 때문이다.
OAuth2ResourceServerConfigurer 를 추가해서 init , configure 메소드를 HttpSecurity가 dobuild 메소드를 통해 호출해주면 필요한 JwtAuthenticationProvider,  BearerTokenAuthenticationFilter  같은 것들을 만들게 된다.

@Bean
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
JwtDecoder jwtDecoderByJwkKeySetUri() {
   NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
         .jwsAlgorithms(this::jwsAlgorithms).build();
   String issuerUri = this.properties.getIssuerUri();
   Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
         ? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
   nimbusJwtDecoder.setJwtValidator(getValidators(defaultValidator));
   return nimbusJwtDecoder;
}
@Bean
@Conditional(IssuerUriCondition.class)
SupplierJwtDecoder jwtDecoderByIssuerUri() {
   return new SupplierJwtDecoder(() -> {
      String issuerUri = this.properties.getIssuerUri();
      NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
      jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri)));
      return jwtDecoder;
   });
}

JwtDecoder 구현체(bean)을 만드는곳은 여러가지이다. @Conditional의 조건에 따라서 만들어지는 구현체가 달라지게 된다. 1번째 것은 application.yaml에 jwk-set-uri 가 있는경우이고 2번째 것은 issuer-uri만 있는 경우이다.

public class IssuerUriCondition extends SpringBootCondition {

   @Override
   public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
      ConditionMessage.Builder message = ConditionMessage.forCondition("OpenID Connect Issuer URI Condition");
      Environment environment = context.getEnvironment();
      String issuerUri = environment.getProperty("spring.security.oauth2.resourceserver.jwt.issuer-uri");
      String jwkSetUri = environment.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri");
      if (!StringUtils.hasText(issuerUri)) {
         return ConditionOutcome.noMatch(message.didNotFind("issuer-uri property").atAll());
      }
      if (StringUtils.hasText(jwkSetUri)) {
         return ConditionOutcome.noMatch(message.found("jwk-set-uri property").items(jwkSetUri));
      }
      return ConditionOutcome.match(message.foundExactly("issuer-uri property"));
   }
}

둘다 설정한 경우 jwk-set-uri가 우선순위가 높다. 먼저 if에 걸려 return 되기 때문이다.

 

 

 

728x90