Skip to content

Commit 2b628f6

Browse files
committed
Resource Server Jwt Support
Introducing initial support for Jwt-Encoded Bearer Token authorization with remote JWK set signature verification. High-level features include: - Accepting bearer tokens as headers and form or query parameters - Verifying signatures from a remote Jwk set And: - A DSL for easy configuration - A sample to demonstrate usage Fixes: spring-projectsgh-5128 Fixes: spring-projectsgh-5125 Fixes: spring-projectsgh-5121 Fixes: spring-projectsgh-5130 Fixes: spring-projectsgh-5226 Fixes: spring-projectsgh-5237
1 parent 51b75c1 commit 2b628f6

File tree

50 files changed

+4101
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4101
-0
lines changed

config/spring-security-config.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
optional project(':spring-security-messaging')
1414
optional project(':spring-security-oauth2-client')
1515
optional project(':spring-security-oauth2-jose')
16+
optional project(':spring-security-oauth2-resource-server')
1617
optional project(':spring-security-openid')
1718
optional project(':spring-security-web')
1819
optional 'io.projectreactor:reactor-core'

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/OAuth2Configurer.java

+29
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2222
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
2323
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
24+
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
2425

2526
/**
2627
* An {@link AbstractHttpConfigurer} that provides support for the
@@ -40,6 +41,8 @@ public final class OAuth2Configurer<B extends HttpSecurityBuilder<B>>
4041

4142
private OAuth2ClientConfigurer<B> clientConfigurer;
4243

44+
private OAuth2ResourceServerConfigurer<B> resourceServerConfigurer;
45+
4346
/**
4447
* Returns the {@link OAuth2ClientConfigurer} for configuring OAuth 2.0 Client support.
4548
*
@@ -52,23 +55,49 @@ public OAuth2ClientConfigurer<B> client() {
5255
return this.clientConfigurer;
5356
}
5457

58+
/**
59+
* Returns the {@link OAuth2ResourceServerConfigurer} for configuring OAuth 2.0 Resource Server support.
60+
*
61+
* @return the {@link OAuth2ResourceServerConfigurer}
62+
*/
63+
public OAuth2ResourceServerConfigurer<B> resourceServer() {
64+
if (this.resourceServerConfigurer == null) {
65+
this.initResourceServerConfigurer();
66+
}
67+
return this.resourceServerConfigurer;
68+
}
69+
5570
@Override
5671
public void init(B builder) throws Exception {
5772
if (this.clientConfigurer != null) {
5873
this.clientConfigurer.init(builder);
5974
}
75+
76+
if (this.resourceServerConfigurer != null) {
77+
this.resourceServerConfigurer.init(builder);
78+
}
6079
}
6180

6281
@Override
6382
public void configure(B builder) throws Exception {
6483
if (this.clientConfigurer != null) {
6584
this.clientConfigurer.configure(builder);
6685
}
86+
87+
if (this.resourceServerConfigurer != null) {
88+
this.resourceServerConfigurer.configure(builder);
89+
}
6790
}
6891

6992
private void initClientConfigurer() {
7093
this.clientConfigurer = new OAuth2ClientConfigurer<>();
7194
this.clientConfigurer.setBuilder(this.getBuilder());
7295
this.clientConfigurer.addObjectPostProcessor(this.objectPostProcessor);
7396
}
97+
98+
private void initResourceServerConfigurer() {
99+
this.resourceServerConfigurer = new OAuth2ResourceServerConfigurer<>();
100+
this.resourceServerConfigurer.setBuilder(this.getBuilder());
101+
this.resourceServerConfigurer.addObjectPostProcessor(this.objectPostProcessor);
102+
}
74103
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web.configurers.oauth2.server.resource;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.security.authentication.AuthenticationManager;
22+
import org.springframework.security.authentication.AuthenticationProvider;
23+
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
24+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
25+
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
26+
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
27+
import org.springframework.security.config.http.SessionCreationPolicy;
28+
import org.springframework.security.oauth2.jwt.JwtDecoder;
29+
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
30+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
31+
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
32+
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
33+
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
34+
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
35+
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
36+
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
37+
import org.springframework.security.web.util.matcher.RequestMatcher;
38+
import org.springframework.util.Assert;
39+
40+
/**
41+
*
42+
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
43+
*
44+
* By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to parse the request
45+
* for bearer tokens and make an authentication attempt.
46+
*
47+
* <p>
48+
* The following configuration options are available:
49+
*
50+
* <ul>
51+
* <li>{@link #jwt()} - enables Jwt-encoded bearer token support</li>
52+
* </ul>
53+
*
54+
* <p>
55+
* When using {@link #jwt()}, a Jwk Set Uri must be supplied via {@link JwtConfigurer#jwkSetUri}
56+
*
57+
* <h2>Security Filters</h2>
58+
*
59+
* The following {@code Filter}s are populated when {@link #jwt()} is configured:
60+
*
61+
* <ul>
62+
* <li>{@link BearerTokenAuthenticationFilter}</li>
63+
* </ul>
64+
*
65+
* <h2>Shared Objects Created</h2>
66+
*
67+
* The following shared objects are populated:
68+
*
69+
* <ul>
70+
* <li>{@link SessionCreationPolicy} (optional)</li>
71+
* </ul>
72+
*
73+
* <h2>Shared Objects Used</h2>
74+
*
75+
* The following shared objects are used:
76+
*
77+
* <ul>
78+
* <li>{@link AuthenticationManager}</li>
79+
* </ul>
80+
*
81+
* If {@link #jwt()} isn't supplied, then the {@link BearerTokenAuthenticationFilter} is still added, but without
82+
* any OAuth 2.0 {@link AuthenticationProvider}s. This is useful if needing to switch out Spring Security's Jwt support
83+
* for a custom one.
84+
*
85+
* @author Josh Cummings
86+
* @since 5.1
87+
* @see BearerTokenAuthenticationFilter
88+
* @see JwtAuthenticationProvider
89+
* @see NimbusJwtDecoderJwkSupport
90+
* @see AbstractHttpConfigurer
91+
*/
92+
public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<H>> extends
93+
AbstractHttpConfigurer<OAuth2ResourceServerConfigurer<H>, H> {
94+
95+
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
96+
private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
97+
98+
private BearerTokenAuthenticationEntryPoint authenticationEntryPoint
99+
= new BearerTokenAuthenticationEntryPoint();
100+
101+
private BearerTokenAccessDeniedHandler accessDeniedHandler
102+
= new BearerTokenAccessDeniedHandler();
103+
104+
private JwtConfigurer jwtConfigurer = new JwtConfigurer();
105+
106+
public JwtConfigurer jwt() {
107+
return this.jwtConfigurer;
108+
}
109+
110+
@Override
111+
public void setBuilder(H http) {
112+
super.setBuilder(http);
113+
initSessionCreationPolicy(http);
114+
}
115+
116+
@Override
117+
public void init(H http) throws Exception {
118+
registerDefaultDeniedHandler(http);
119+
registerDefaultEntryPoint(http);
120+
registerDefaultCsrfOverride(http);
121+
}
122+
123+
@Override
124+
public void configure(H http) throws Exception {
125+
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
126+
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
127+
128+
AuthenticationManager manager = http.getSharedObject(AuthenticationManager.class);
129+
130+
BearerTokenAuthenticationFilter filter =
131+
new BearerTokenAuthenticationFilter(manager);
132+
filter.setBearerTokenResolver(bearerTokenResolver);
133+
filter = postProcess(filter);
134+
135+
http.addFilterBefore(filter, BasicAuthenticationFilter.class);
136+
137+
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
138+
139+
if (decoder != null) {
140+
JwtAuthenticationProvider provider =
141+
new JwtAuthenticationProvider(decoder);
142+
provider = postProcess(provider);
143+
144+
http.authenticationProvider(provider);
145+
} else {
146+
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
147+
"in Spring Security and no instance of JwtDecoder could be found. Make sure to specify " +
148+
"a jwk set uri by doing http.oauth2().resourceServer().jwt().jwkSetUri(uri)");
149+
}
150+
}
151+
152+
public class JwtConfigurer {
153+
private JwtDecoder decoder;
154+
155+
private JwtConfigurer() {}
156+
157+
public OAuth2ResourceServerConfigurer<H> jwkSetUri(String uri) {
158+
this.decoder = new NimbusJwtDecoderJwkSupport(uri);
159+
return OAuth2ResourceServerConfigurer.this;
160+
}
161+
162+
private JwtDecoder getJwtDecoder() {
163+
return this.decoder;
164+
}
165+
}
166+
167+
private void initSessionCreationPolicy(H http) {
168+
if (http.getSharedObject(SessionCreationPolicy.class) == null) {
169+
http.setSharedObject(SessionCreationPolicy.class, SessionCreationPolicy.STATELESS);
170+
}
171+
}
172+
173+
private void registerDefaultDeniedHandler(H http) {
174+
ExceptionHandlingConfigurer<H> exceptionHandling = http
175+
.getConfigurer(ExceptionHandlingConfigurer.class);
176+
if (exceptionHandling == null) {
177+
return;
178+
}
179+
180+
exceptionHandling.defaultAccessDeniedHandlerFor(
181+
this.accessDeniedHandler,
182+
this.requestMatcher);
183+
}
184+
185+
private void registerDefaultEntryPoint(H http) {
186+
ExceptionHandlingConfigurer<H> exceptionHandling = http
187+
.getConfigurer(ExceptionHandlingConfigurer.class);
188+
if (exceptionHandling == null) {
189+
return;
190+
}
191+
192+
exceptionHandling.defaultAuthenticationEntryPointFor(
193+
this.authenticationEntryPoint,
194+
this.requestMatcher);
195+
}
196+
197+
private void registerDefaultCsrfOverride(H http) {
198+
CsrfConfigurer<H> csrf = http
199+
.getConfigurer(CsrfConfigurer.class);
200+
if (csrf == null) {
201+
return;
202+
}
203+
204+
csrf.ignoringRequestMatchers(this.requestMatcher);
205+
}
206+
207+
private BearerTokenResolver getBearerTokenResolver() {
208+
return this.bearerTokenResolver;
209+
}
210+
211+
private static final class BearerTokenRequestMatcher implements RequestMatcher {
212+
private BearerTokenResolver bearerTokenResolver
213+
= new DefaultBearerTokenResolver();
214+
215+
@Override
216+
public boolean matches(HttpServletRequest request) {
217+
return this.bearerTokenResolver.resolve(request) != null;
218+
}
219+
220+
public void setBearerTokenResolver(BearerTokenResolver tokenResolver) {
221+
Assert.notNull(tokenResolver, "resolver cannot be null");
222+
this.bearerTokenResolver = tokenResolver;
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)