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
현재 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 되기 때문이다.
'WEB > Security' 카테고리의 다른 글
OAuth 2.0 Resource Server - 검증 기초 (0) | 2022.12.23 |
---|---|
OAuth 2.0 Resource Server API - jwt() (0) | 2022.12.22 |
OAuth 2.0 Client - Social Login (Google, Naver, KaKao) + FormLogin (1) | 2022.12.19 |
OAuth 2.0 Client - oauth2Client() (1) | 2022.12.14 |
OAuth2.0 Client - oauth2Login() + API 커스텀 구현 (0) | 2022.11.23 |