Skip to content

oauth2ResourceServer JWT revocation built-in support #10558

Closed
@ah1508

Description

@ah1508

Dear all,

JWT revocation can be implemented manually with a publish/subscribe : a revocation event is sent (payload is the token to revoke) and subscribers (API that may receive this token and must reject it) receive this event so they can keep an up to date revocation list. Elements are automatically removed when exp is before Instant.now()

A built-in support for publish/subscribe could be a part of the in-development spring oidc server.

But on the spring security side, as far as I know it must be coded manually. Example (TTL omitted) :

Set<String> revoked = new HashSet<>();
	
@Bean
RouterFunction<ServerResponse> revocations(){
	return RouterFunctions.route(RequestPredicates.POST("/revocations"), req -> {
		String token = req.body(String.class); // content-type is text/plain
		revoked.add(token);
		return ServerResponse.noContent().build();
	});
}
	
@Bean
WebSecurityConfigurerAdapter securityConfigurerAdapter(JwtDecoder jwtDecoder /*created by auto-configuration*/) {
	return new WebSecurityConfigurerAdapter() {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http.oauth2ResourceServer(cust -> {
				cust.jwt(jwtCust -> {
					jwtCust.decoder(token -> {
						if(revoked.contains(token)) {
							throw new SecurityException();
						}
						return jwtDecoder.decode(token);
					});
				});
			});
			http.csrf().disable();
		}
	};
}

How about a built-in support in spring Security ?

If the revocation endpoint and the revocations set are declared manually the configuration would looks like :

Set<String> revoked = new HashSet<>();

@Bean
RouterFunction<ServerResponse> revocations() {
	return RouterFunctions.route(RequestPredicates.POST("/revocations"), req -> {
		String token = req.body(String.class);
		revoked.add(token);
		return ServerResponse.noContent().build();
	});
}

// omitted : scheduled removal of expired tokens

@Bean
WebSecurityConfigurerAdapter securityConfigurerAdapter(JwtDecoder jwtDecoder){
	return new WebSecurityConfigurerAdapter() {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http.oauth2ResourceServer(cust -> {
				cust.jwt(jwtCust -> {
					jwtCust.setRevocations(this.revoked);
				});
			});
			http.csrf().disable();
		}
	};
}

If the revocation endpoint is built-in, like an actuator :

@Bean
WebSecurityConfigurerAdapter securityConfigurerAdapter(){
	return new WebSecurityConfigurerAdapter() {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http.oauth2ResourceServer(cust -> {
				cust.jwt(jwtCust -> {
					jwtCust.enableRevocations();
				});
			});
			http.csrf().disable();
		}
	};
}

With a customizer :

cust.jwt(jwtCust -> {
	jwtCust.enableRevocations(revocationCustomizer -> {
		revocationCustomizer.rejectIf(token -> /*code that return true or false*/);
		revocationCustomizer.rejectResponseHandler(token -> ResponseEntity.status(401).header("WWW-Authenticate", "Bearer error=\"invalid_token\", error_description=\"An error occurred while attempting to decode the Jwt: The token is revoked!\", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"").build());
        });
});

and configuration properties would allow to configure revocation endpoint path and RBAC for this endpoint (exemple : hasRole('admin')).

Metadata

Metadata

Assignees

Labels

in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)status: declinedA suggestion or change that we don't feel we should currently applytype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions