Based on the Spring Tips Youtube video Spring Tips: The Spring Authorization Server I want to simplify the setup for didactic reasons.
For background: I teach Spring development. Prior to teaching Spring Security's OAuth2 support I will have shown my students a simple Jwt-based application which houses both authorization as well as the secured resources. As a natural progression I want to then show how OAUth2 splits up authorization and resource hosting with its protocol.
Ideally, I would want to first show how Authorization Grant Type "implicit grant" works, as it seems like the most natural progression. But of course with the latest OAUth2.1 standard it is no longer supported by the standard and as a result, neither by Spring Security and for good reasons.
Ok, so "Authorization Code" type it is.
This flow entails many behind-the scenes roundtrips which, for didactic reasons, I want to reduce in the first iteration of my sample project (later of course I will show how all of the many roundtrips make sense and enhance the protocol).
In my preceding Jwt-application I configured both JwtDecoder
and JwtEncoder
like so:
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(jwtConfigProperties.publicKey()).build();
return jwtDecoder;
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(jwtConfigProperties.publicKey()).privateKey(jwtConfigProperties.privateKey()).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
with
@ConfigurationProperties(prefix = "rsa")
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
}
and application.properties entries
rsa.private-key= classpath:certs/privateKey.pem
rsa.public-key= classpath:certs/publicKey.pem
Those keys are present, as is of course:
@SpringBootApplication
@EnableConfigurationProperties(RsaKeyProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Everything works fine. Now I want to reuse this code in my OAUth2 project for the authorization server (which needs the JwtEncoder) and the resource server (which needs the JwtDecoder).
Since both authorization server and resource server would use matching keys in this scenario, the OAuth2 GET /oauth2/jwks
requests (I counted 2 of them in my TCP/IP monitor) would not be needed anymore and could thus be omitted, which is exactly what I want to achieve.
I am struggling with this however.
Neither stackoverflow, nor web-search and in particular not AI (neither IntelliJ IDEA AI assistant nor GitHub Copilot) were of any help, so I am turning to stackoverflow for help.
I gladly provide the entire configuration for all 3 server (authorization-, resource- and client-server). It is what Josh Long showed in his video (NOT what is in the accompanying GitHup-project though, apparently he enhanced that one. I really had to follow the video 1:1)
Based on the Spring Tips Youtube video Spring Tips: The Spring Authorization Server I want to simplify the setup for didactic reasons.
For background: I teach Spring development. Prior to teaching Spring Security's OAuth2 support I will have shown my students a simple Jwt-based application which houses both authorization as well as the secured resources. As a natural progression I want to then show how OAUth2 splits up authorization and resource hosting with its protocol.
Ideally, I would want to first show how Authorization Grant Type "implicit grant" works, as it seems like the most natural progression. But of course with the latest OAUth2.1 standard it is no longer supported by the standard and as a result, neither by Spring Security and for good reasons.
Ok, so "Authorization Code" type it is.
This flow entails many behind-the scenes roundtrips which, for didactic reasons, I want to reduce in the first iteration of my sample project (later of course I will show how all of the many roundtrips make sense and enhance the protocol).
In my preceding Jwt-application I configured both JwtDecoder
and JwtEncoder
like so:
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(jwtConfigProperties.publicKey()).build();
return jwtDecoder;
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(jwtConfigProperties.publicKey()).privateKey(jwtConfigProperties.privateKey()).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
with
@ConfigurationProperties(prefix = "rsa")
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
}
and application.properties entries
rsa.private-key= classpath:certs/privateKey.pem
rsa.public-key= classpath:certs/publicKey.pem
Those keys are present, as is of course:
@SpringBootApplication
@EnableConfigurationProperties(RsaKeyProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Everything works fine. Now I want to reuse this code in my OAUth2 project for the authorization server (which needs the JwtEncoder) and the resource server (which needs the JwtDecoder).
Since both authorization server and resource server would use matching keys in this scenario, the OAuth2 GET /oauth2/jwks
requests (I counted 2 of them in my TCP/IP monitor) would not be needed anymore and could thus be omitted, which is exactly what I want to achieve.
I am struggling with this however.
Neither stackoverflow, nor web-search and in particular not AI (neither IntelliJ IDEA AI assistant nor GitHub Copilot) were of any help, so I am turning to stackoverflow for help.
I gladly provide the entire configuration for all 3 server (authorization-, resource- and client-server). It is what Josh Long showed in his video (NOT what is in the accompanying GitHup-project though, apparently he enhanced that one. I really had to follow the video 1:1)
Share Improve this question edited Feb 17 at 9:26 chris457 asked Feb 17 at 9:20 chris457chris457 768 bronze badges 10- just a question of clarfication, you are saying "matching keys" are you referring to symmetric or asymmetric keys as the term can be ambigous? And i dont really understand your question, are you saying that your resource server is doing JWK requests under the hood even though you have configured your resource server to use a public key? ` – Toerktumlare Commented Feb 17 at 9:46
- Yes, these are asymetric keys, as could be seen with RSAPublicKey publicKey, RSAPrivateKey privateKey. And yes, my resource server is doing JWK requests under the hood despite configuring JwtDecoder and JwtEncoder beans . So I suspect I haven't properly configured my resource server to use a public key, hence my question. I am using spring-boot-starter-oauth2-authorization-server and spring-boot-starter-oauth2-resource-server with spring-boot-starter-oauth2-client for the client. Maybe I am just missing a certain property or two. That would be ideal. – chris457 Commented Feb 17 at 10:12
- well a resource server does not really need to use a private key as it is not an issuer, that issues tokens and needs to sign them. So thats confusing me a bit. Do you have any debuglogs you can share where we can se who is doing the calls? Here is a tutorial repo i did with a resource server that you can compare with github/Toerktumlare/spring-security-jwt-demo/blob/main/src/… – Toerktumlare Commented Feb 17 at 12:04
- @Toerktumlare Yes, I know, for the resource server I only used the public key. I did not mention that to keep the question short (and having a private key on resource server side wouldn't hurt, since its not used there, still I only have @ConfigurationProperties(prefix = "rsa") public record RsaKeyProperties(RSAPublicKey publicKey) { } on resource server side. ). Thank you for your example. It is a pure Jwt-example, which I have myself. I really need the correct OAuth2 authorization server + resource server configuration for respective static keys as needed by those respective servers. – chris457 Commented Feb 17 at 12:21
- 1 If your concern is easing your students' path, 1) choose an already implemented authorization server 2) take them directly to a secured point (that includes rotating keys and carefully avoiding hand-crafted JWTs), when iterating to it, most will stop in the middle and go to prod with unsafe "solutions" 3) take the flows out of the picture when talking about a resource server: authentication (how tokens are issued) is not a concern for resource servers, solely authorization is (token validation and resource access evaluation) – ch4mp Commented Feb 17 at 19:44
2 Answers
Reset to default 0When your application includes the spring-boot-starter-oauth2-resource-server dependency, this Spring Boot starter looks for certain application properties and configures a JWT decoder in the application context using those properties.
If you want the resource server to read the public key from a local file instead of getting it from the JWKS endpoint, first unset the property:
# Writing in YAML syntax for clarity because the property name is very long.
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri:
and instead set the property:
spring:
security:
oauth2:
resourceserver:
jwt:
# DO NOT USE IN PRODUCTION.
public-key-location: classpath:certs/publicKey.pem
While experimenting with apps based on these sample apps:
- demo-authorizationserver
- messages-resource
- demo-client
from spring-authorization-server/samples, adding a custom JWKSource<SecurityContext>
bean (and nothing else) to the authorization server, appears to achieve the desired behavior.
When logging in with the demo-client, you’ll see Inside JWKSource#get
in the logs. Additionally, there will be no extra requests to /oauth2/jwks
when accessing the resource server.
Note: Keys should be rotated regularly, so this code is not suitable for production use.
private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerConfig.class);
@Bean
public JWKSource<SecurityContext> jwkSource(
@Value("${classpath:certs/public-key.pem}") RSAPublicKey publicKey,
@Value("${classpath:certs/private-key.pem}") RSAPrivateKey privateKey) {
var rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
var jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> {
LOG.info("Inside JWKSource#get");
return jwkSelector.select(jwkSet);
};
}
application.yml in resource server
In the resource server, Spring Boot will auto-configures a JwtDecoder
bean with this:
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:certs/public-key.pem