16
16
17
17
package org .springframework .security .config .web .server ;
18
18
19
+ import java .io .IOException ;
20
+ import java .io .PrintWriter ;
21
+ import java .io .StringWriter ;
22
+ import java .security .interfaces .RSAPublicKey ;
23
+ import java .time .Duration ;
24
+ import java .util .ArrayList ;
25
+ import java .util .Arrays ;
26
+ import java .util .Collections ;
27
+ import java .util .HashMap ;
28
+ import java .util .List ;
29
+ import java .util .Map ;
30
+ import java .util .Optional ;
31
+ import java .util .UUID ;
32
+ import java .util .function .Function ;
33
+
34
+ import reactor .core .publisher .Mono ;
35
+ import reactor .util .context .Context ;
36
+
19
37
import org .springframework .beans .BeansException ;
20
38
import org .springframework .context .ApplicationContext ;
21
39
import org .springframework .core .Ordered ;
65
83
import org .springframework .security .oauth2 .jwt .ReactiveJwtDecoderFactory ;
66
84
import org .springframework .security .oauth2 .server .resource .authentication .JwtAuthenticationConverter ;
67
85
import org .springframework .security .oauth2 .server .resource .authentication .JwtReactiveAuthenticationManager ;
86
+ import org .springframework .security .oauth2 .server .resource .authentication .OAuth2IntrospectionReactiveAuthenticationManager ;
68
87
import org .springframework .security .oauth2 .server .resource .authentication .ReactiveJwtAuthenticationConverterAdapter ;
69
88
import org .springframework .security .oauth2 .server .resource .web .access .server .BearerTokenServerAccessDeniedHandler ;
70
89
import org .springframework .security .oauth2 .server .resource .web .server .BearerTokenServerAuthenticationEntryPoint ;
143
162
import org .springframework .web .server .ServerWebExchange ;
144
163
import org .springframework .web .server .WebFilter ;
145
164
import org .springframework .web .server .WebFilterChain ;
146
- import reactor .core .publisher .Mono ;
147
- import reactor .util .context .Context ;
148
-
149
- import java .io .IOException ;
150
- import java .io .PrintWriter ;
151
- import java .io .StringWriter ;
152
- import java .security .interfaces .RSAPublicKey ;
153
- import java .time .Duration ;
154
- import java .util .ArrayList ;
155
- import java .util .Arrays ;
156
- import java .util .Collections ;
157
- import java .util .HashMap ;
158
- import java .util .List ;
159
- import java .util .Map ;
160
- import java .util .Optional ;
161
- import java .util .UUID ;
162
- import java .util .function .Function ;
163
165
164
166
import static org .springframework .security .web .server .DelegatingServerAuthenticationEntryPoint .DelegateEntry ;
165
167
import static org .springframework .security .web .server .util .matcher .ServerWebExchangeMatcher .MatchResult .match ;
@@ -994,8 +996,11 @@ public class OAuth2ResourceServerSpec {
994
996
private ServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint ();
995
997
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler ();
996
998
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter ();
999
+ private BearerTokenServerWebExchangeMatcher bearerTokenServerWebExchangeMatcher =
1000
+ new BearerTokenServerWebExchangeMatcher ();
997
1001
998
1002
private JwtSpec jwt ;
1003
+ private OpaqueTokenSpec opaqueToken ;
999
1004
1000
1005
/**
1001
1006
* Configures the {@link ServerAccessDeniedHandler} to use for requests authenticating with
@@ -1047,10 +1052,94 @@ public JwtSpec jwt() {
1047
1052
return this .jwt ;
1048
1053
}
1049
1054
1055
+ public OpaqueTokenSpec opaqueToken () {
1056
+ if (this .opaqueToken == null ) {
1057
+ this .opaqueToken = new OpaqueTokenSpec ();
1058
+ }
1059
+ return this .opaqueToken ;
1060
+ }
1061
+
1050
1062
protected void configure (ServerHttpSecurity http ) {
1063
+ this .bearerTokenServerWebExchangeMatcher
1064
+ .setBearerTokenConverter (this .bearerTokenConverter );
1065
+
1066
+ registerDefaultAccessDeniedHandler (http );
1067
+ registerDefaultAuthenticationEntryPoint (http );
1068
+ registerDefaultCsrfOverride (http );
1069
+
1070
+ if (this .jwt != null && this .opaqueToken != null ) {
1071
+ throw new IllegalStateException ("Spring Security only supports JWTs or Opaque Tokens, not both at the " +
1072
+ "same time" );
1073
+ }
1074
+
1075
+ if (this .jwt == null && this .opaqueToken == null ) {
1076
+ throw new IllegalStateException ("Jwt and Opaque Token are the only supported formats for bearer tokens " +
1077
+ "in Spring Security and neither was found. Make sure to configure JWT " +
1078
+ "via http.oauth2ResourceServer().jwt() or Opaque Tokens via " +
1079
+ "http.oauth2ResourceServer().opaqueToken()." );
1080
+ }
1081
+
1051
1082
if (this .jwt != null ) {
1052
1083
this .jwt .configure (http );
1053
1084
}
1085
+
1086
+ if (this .opaqueToken != null ) {
1087
+ this .opaqueToken .configure (http );
1088
+ }
1089
+ }
1090
+
1091
+ private void registerDefaultAccessDeniedHandler (ServerHttpSecurity http ) {
1092
+ if ( http .exceptionHandling != null ) {
1093
+ http .defaultAccessDeniedHandlers .add (
1094
+ new ServerWebExchangeDelegatingServerAccessDeniedHandler .DelegateEntry (
1095
+ this .bearerTokenServerWebExchangeMatcher ,
1096
+ OAuth2ResourceServerSpec .this .accessDeniedHandler
1097
+ )
1098
+ );
1099
+ }
1100
+ }
1101
+
1102
+ private void registerDefaultAuthenticationEntryPoint (ServerHttpSecurity http ) {
1103
+ if (http .exceptionHandling != null ) {
1104
+ http .defaultEntryPoints .add (
1105
+ new DelegateEntry (
1106
+ this .bearerTokenServerWebExchangeMatcher ,
1107
+ OAuth2ResourceServerSpec .this .entryPoint
1108
+ )
1109
+ );
1110
+ }
1111
+ }
1112
+
1113
+ private void registerDefaultCsrfOverride (ServerHttpSecurity http ) {
1114
+ if ( http .csrf != null && !http .csrf .specifiedRequireCsrfProtectionMatcher ) {
1115
+ http
1116
+ .csrf ()
1117
+ .requireCsrfProtectionMatcher (
1118
+ new AndServerWebExchangeMatcher (
1119
+ CsrfWebFilter .DEFAULT_CSRF_MATCHER ,
1120
+ new NegatedServerWebExchangeMatcher (
1121
+ this .bearerTokenServerWebExchangeMatcher )));
1122
+ }
1123
+ }
1124
+
1125
+ private class BearerTokenServerWebExchangeMatcher implements ServerWebExchangeMatcher {
1126
+ ServerAuthenticationConverter bearerTokenConverter ;
1127
+
1128
+ @ Override
1129
+ public Mono <MatchResult > matches (ServerWebExchange exchange ) {
1130
+ return this .bearerTokenConverter .convert (exchange )
1131
+ .flatMap (this ::nullAuthentication )
1132
+ .onErrorResume (e -> notMatch ());
1133
+ }
1134
+
1135
+ public void setBearerTokenConverter (ServerAuthenticationConverter bearerTokenConverter ) {
1136
+ Assert .notNull (bearerTokenConverter , "bearerTokenConverter cannot be null" );
1137
+ this .bearerTokenConverter = bearerTokenConverter ;
1138
+ }
1139
+
1140
+ private Mono <MatchResult > nullAuthentication (Authentication authentication ) {
1141
+ return authentication == null ? notMatch () : match ();
1142
+ }
1054
1143
}
1055
1144
1056
1145
/**
@@ -1062,9 +1151,6 @@ public class JwtSpec {
1062
1151
private Converter <Jwt , ? extends Mono <? extends AbstractAuthenticationToken >> jwtAuthenticationConverter
1063
1152
= new ReactiveJwtAuthenticationConverterAdapter (new JwtAuthenticationConverter ());
1064
1153
1065
- private BearerTokenServerWebExchangeMatcher bearerTokenServerWebExchangeMatcher =
1066
- new BearerTokenServerWebExchangeMatcher ();
1067
-
1068
1154
/**
1069
1155
* Configures the {@link ReactiveAuthenticationManager} to use
1070
1156
* @param authenticationManager the authentication manager to use
@@ -1128,17 +1214,10 @@ public OAuth2ResourceServerSpec and() {
1128
1214
}
1129
1215
1130
1216
protected void configure (ServerHttpSecurity http ) {
1131
- this .bearerTokenServerWebExchangeMatcher .setBearerTokenConverter (bearerTokenConverter );
1132
-
1133
- registerDefaultAccessDeniedHandler (http );
1134
- registerDefaultAuthenticationEntryPoint (http );
1135
- registerDefaultCsrfOverride (http );
1136
-
1137
1217
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager ();
1138
1218
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter (authenticationManager );
1139
1219
oauth2 .setServerAuthenticationConverter (bearerTokenConverter );
1140
1220
oauth2 .setAuthenticationFailureHandler (new ServerAuthenticationEntryPointFailureHandler (entryPoint ));
1141
-
1142
1221
http
1143
1222
.addFilterAt (oauth2 , SecurityWebFiltersOrder .AUTHENTICATION );
1144
1223
}
@@ -1170,59 +1249,63 @@ private ReactiveAuthenticationManager getAuthenticationManager() {
1170
1249
1171
1250
return authenticationManager ;
1172
1251
}
1252
+ }
1173
1253
1174
- private void registerDefaultAccessDeniedHandler ( ServerHttpSecurity http ) {
1175
- if ( http . exceptionHandling != null ) {
1176
- http . defaultAccessDeniedHandlers . add (
1177
- new ServerWebExchangeDelegatingServerAccessDeniedHandler . DelegateEntry (
1178
- this . bearerTokenServerWebExchangeMatcher ,
1179
- OAuth2ResourceServerSpec . this . accessDeniedHandler
1180
- )
1181
- ) ;
1182
- }
1183
- }
1254
+ /**
1255
+ * Configures Opaque Token Resource Server support
1256
+ *
1257
+ * @author Josh Cummings
1258
+ * @since 5.2
1259
+ */
1260
+ public class OpaqueTokenSpec {
1261
+ private String introspectionUri ;
1262
+ private String introspectionClientId ;
1263
+ private String introspectionClientSecret ;
1184
1264
1185
- private void registerDefaultAuthenticationEntryPoint ( ServerHttpSecurity http ) {
1186
- if ( http . exceptionHandling != null ) {
1187
- http . defaultEntryPoints . add (
1188
- new DelegateEntry (
1189
- this . bearerTokenServerWebExchangeMatcher ,
1190
- OAuth2ResourceServerSpec . this . entryPoint
1191
- )
1192
- ) ;
1193
- }
1265
+ /**
1266
+ * Configures the URI of the Introspection endpoint
1267
+ * @param introspectionUri The URI of the Introspection endpoint
1268
+ * @return the {@code OpaqueTokenSpec} for additional configuration
1269
+ */
1270
+ public OpaqueTokenSpec introspectionUri ( String introspectionUri ) {
1271
+ Assert . hasText ( introspectionUri , "introspectionUri cannot be empty" );
1272
+ this . introspectionUri = introspectionUri ;
1273
+ return this ;
1194
1274
}
1195
1275
1196
- private void registerDefaultCsrfOverride (ServerHttpSecurity http ) {
1197
- if ( http .csrf != null && !http .csrf .specifiedRequireCsrfProtectionMatcher ) {
1198
- http
1199
- .csrf ()
1200
- .requireCsrfProtectionMatcher (
1201
- new AndServerWebExchangeMatcher (
1202
- CsrfWebFilter .DEFAULT_CSRF_MATCHER ,
1203
- new NegatedServerWebExchangeMatcher (
1204
- this .bearerTokenServerWebExchangeMatcher )));
1205
- }
1276
+ /**
1277
+ * Configures the credentials for Introspection endpoint
1278
+ * @param clientId The clientId part of the credentials
1279
+ * @param clientSecret The clientSecret part of the credentials
1280
+ * @return the {@code OpaqueTokenSpec} for additional configuration
1281
+ */
1282
+ public OpaqueTokenSpec introspectionClientCredentials (String clientId , String clientSecret ) {
1283
+ Assert .hasText (clientId , "clientId cannot be empty" );
1284
+ Assert .notNull (clientSecret , "clientSecret cannot be null" );
1285
+ this .introspectionClientId = clientId ;
1286
+ this .introspectionClientSecret = clientSecret ;
1287
+ return this ;
1206
1288
}
1207
1289
1208
- private class BearerTokenServerWebExchangeMatcher implements ServerWebExchangeMatcher {
1209
- ServerAuthenticationConverter bearerTokenConverter ;
1210
-
1211
- @ Override
1212
- public Mono <MatchResult > matches (ServerWebExchange exchange ) {
1213
- return this .bearerTokenConverter .convert (exchange )
1214
- .flatMap (this ::nullAuthentication )
1215
- .onErrorResume (e -> notMatch ());
1216
- }
1290
+ /**
1291
+ * Allows method chaining to continue configuring the {@link ServerHttpSecurity}
1292
+ * @return the {@link ServerHttpSecurity} to continue configuring
1293
+ */
1294
+ public OAuth2ResourceServerSpec and () {
1295
+ return OAuth2ResourceServerSpec .this ;
1296
+ }
1217
1297
1218
- public void setBearerTokenConverter ( ServerAuthenticationConverter bearerTokenConverter ) {
1219
- Assert . notNull ( bearerTokenConverter , "bearerTokenConverter cannot be null" );
1220
- this .bearerTokenConverter = bearerTokenConverter ;
1221
- }
1298
+ protected ReactiveAuthenticationManager getAuthenticationManager ( ) {
1299
+ return new OAuth2IntrospectionReactiveAuthenticationManager (
1300
+ this .introspectionUri , this . introspectionClientId , this . introspectionClientSecret ) ;
1301
+ }
1222
1302
1223
- private Mono <MatchResult > nullAuthentication (Authentication authentication ) {
1224
- return authentication == null ? notMatch () : match ();
1225
- }
1303
+ protected void configure (ServerHttpSecurity http ) {
1304
+ ReactiveAuthenticationManager authenticationManager = getAuthenticationManager ();
1305
+ AuthenticationWebFilter oauth2 = new AuthenticationWebFilter (authenticationManager );
1306
+ oauth2 .setServerAuthenticationConverter (bearerTokenConverter );
1307
+ oauth2 .setAuthenticationFailureHandler (new ServerAuthenticationEntryPointFailureHandler (entryPoint ));
1308
+ http .addFilterAt (oauth2 , SecurityWebFiltersOrder .AUTHENTICATION );
1226
1309
}
1227
1310
}
1228
1311
0 commit comments