@@ -128,8 +128,6 @@ import org.springframework.security.oauth2.jwt.Jwt;
128
128
129
129
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
130
130
131
- private final OAuth2Error oAuth2Error = new OAuth2Error("invalid_token", "Required audience not found", null);
132
-
133
131
private final String audience;
134
132
135
133
public AudienceValidator(String audience) {
@@ -139,18 +137,21 @@ public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
139
137
@Override
140
138
public OAuth2TokenValidatorResult validate(Jwt jwt) {
141
139
if (!jwt.getAudience().contains(audience)) {
142
- return OAuth2TokenValidatorResult.failure(oAuth2Error );
140
+ return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Required audience not found", null) );
143
141
}
144
142
143
+ // Optional: For RBAC validate the scopes of the JWT.
144
+ String scopes = jwt.getClaimAsString("scope");
145
+ if (scopes == null || !scopes.contains("read:profile")) {
146
+ return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Insufficient permission", null));
147
+ }
148
+
149
+
145
150
return OAuth2TokenValidatorResult.success();
146
151
}
147
152
}
148
153
` ` `
149
154
150
- :::note
151
- For [🔐 RBAC](/docs/recipes/rbac), scope validation is also required.
152
- :: :
153
-
154
155
# # Configure Spring Security
155
156
156
157
Spring Security makes it easy to configure your application as a Resource Server
@@ -173,18 +174,19 @@ import com.nimbusds.jose.proc.SecurityContext;
173
174
import io.logto.springboot.sample.validator.AudienceValidator;
174
175
import org.springframework.beans.factory.annotation.Value;
175
176
import org.springframework.context.annotation.Bean;
177
+ import org.springframework.security.config.Customizer;
176
178
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
177
179
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
178
180
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
179
181
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
180
182
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
181
- import org.springframework.security.oauth2.jwt.Jwt;
182
183
import org.springframework.security.oauth2.jwt.JwtDecoder;
183
184
import org.springframework.security.oauth2.jwt.JwtValidators;
184
185
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
185
186
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
186
- import org.springframework.security.web.SecurityFilterChain ;
187
+ import org.springframework.security.web.DefaultSecurityFilterChain ;
187
188
189
+ @Configuration
188
190
@EnableWebSecurity
189
191
public class SecurityConfiguration {
190
192
@@ -216,15 +218,17 @@ public class SecurityConfiguration {
216
218
}
217
219
218
220
@Bean
219
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
220
- http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).cors().and()
221
- .authorizeRequests(customizer -> customizer
222
- // Only authenticated requests can access your protected APIs
223
- // e.g. ` http://localhost:3000/` and `http://localhost:3000/profile`.
224
- .mvcMatchers("/", "/secret").authenticated()
225
- // Anyone can access the public profile.
226
- .mvcMatchers("/profile").permitAll()
227
- );
221
+ public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
222
+ http
223
+ .securityMatcher("/api/**")
224
+ .oauth2ResourceServer(oauth2 -> oauth2
225
+ .jwt(Customizer.withDefaults()))
226
+ .authorizeHttpRequests(requests -> requests
227
+ // Allow all requests to the public APIs.
228
+ .requestMatchers("/api/.wellknown/**").permitAll()
229
+ // Require jwt token validation for the protected APIs.
230
+ .anyRequest().authenticated());
231
+
228
232
return http.build();
229
233
}
230
234
}
@@ -247,20 +251,14 @@ import org.springframework.web.bind.annotation.RestController;
247
251
@CrossOrigin(origins = "*")
248
252
@RestController
249
253
public class ProtectedController {
250
-
251
- @GetMapping (" /" )
252
- public String protectedRoot () {
253
- return " Protected root." ;
254
- }
255
-
256
- @GetMapping (" /secret" )
257
- public String protectedSecret () {
258
- return " Protected secret." ;
254
+ @GetMapping("/api/profile")
255
+ public String protectedProfile() {
256
+ return "Protected profile.";
259
257
}
260
258
261
- @GetMapping (" /profile " )
262
- public String publicProfile () {
263
- return " Public profile ." ;
259
+ @GetMapping("/api/.wellknown/config.json ")
260
+ public String publicConfig () {
261
+ return "Public config .";
264
262
}
265
263
}
266
264
` ` `
@@ -297,7 +295,7 @@ Request your protected API with the Access Token as the Bearer token in the Auth
297
295
e.g. execute the `curl` command.
298
296
299
297
` ` ` bash
300
- curl --include ' http://localhost:3000/secret ' \
298
+ curl --include 'http://localhost:3000/api/profile ' \
301
299
--header 'Authorization: Bearer <your-access-token>'
302
300
` ` `
303
301
0 commit comments