0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

refactor(console): update spring boot api protection guide (#6018)

update spring boot api protection guide
This commit is contained in:
simeng-li 2024-06-17 10:14:04 +08:00 committed by GitHub
parent dc6fbe212e
commit a02c20a664
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -35,9 +35,10 @@ dependencies {
Since Spring Boot and Spring Security have built-in support for both OAuth2 resource server and JWT validation,
you DO NOT need to add additional libraries from Logto to integrate.
See [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
and [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture)
for more details.
See [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
and [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture)
for more details.
</InlineNotification>
</Step>
@ -51,14 +52,16 @@ and signed with [JWK](https://datatracker.ietf.org/doc/html/rfc7517)
Before moving on, you will need to get an issuer and a JWKS URI to verify the issuer and the signature of the Bearer Token (`access_token`).
<p>
All the Logto Authorization server configurations can be found by requesting <code>{appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}</code>, including the <strong>issuer</strong>, <strong>jwks_uri</strong> and other authorization configs.
All the Logto Authorization server configurations can be found by requesting{' '}
<code>{appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}</code>, including the{' '}
<strong>issuer</strong>, <strong>jwks_uri</strong> and other authorization configs.
</p>
An example of the response:
<pre>
<code className="language-json">
{`{
{`{
// ...
"issuer": "${appendPath(props.endpoint, '/oidc')}",
"jwks_uri": "${appendPath(props.endpoint, '/oidc/jwks')}"
@ -74,8 +77,8 @@ An example of the response:
Use an `application.yml` file (instead of the default `application.properties`) to configure the server port, audience, and OAuth2 resource server.
<pre>
<code className="language-yaml">
{`# path/to/project/src/main/resources/application.yaml
<code className="language-yaml">
{`# path/to/project/src/main/resources/application.yaml
server:
port: 3000
@ -89,7 +92,7 @@ spring:
jwt:
issuer-uri: ${appendPath(props.endpoint, '/oidc')}
jwk-set-uri: ${appendPath(props.endpoint, '/oidc/jwks')}`}
</code>
</code>
</pre>
- `audience`: The unique API identifier of your protected API resource.
@ -113,8 +116,6 @@ import org.springframework.security.oauth2.jwt.Jwt;
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final OAuth2Error oAuth2Error = new OAuth2Error("invalid_token", "Required audience not found", null);
private final String audience;
public AudienceValidator(String audience) {
@ -124,18 +125,21 @@ public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (!jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.failure(oAuth2Error);
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Required audience not found", null));
}
// Optional: For RBAC validate the scopes of the JWT.
String scopes = jwt.getClaimAsString("scope");
if (scopes == null || !scopes.contains("read:profile")) {
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Insufficient permission", null));
}
return OAuth2TokenValidatorResult.success();
}
}
```
<InlineNotification>
For <a href="https://docs.logto.io/docs/recipes/rbac/" target="_blank" rel="noopener">🔐 RBAC</a>, scope validation is also required.
</InlineNotification>
</Step>
<Step title="Configure Spring Security">
@ -154,17 +158,19 @@ import com.nimbusds.jose.proc.SecurityContext;
import io.logto.springboot.sample.validator.AudienceValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.web.DefaultSecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@ -180,6 +186,8 @@ public class SecurityConfiguration {
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwksUri)
// Logto uses the ES384 algorithm to sign the JWTs by default.
.jwsAlgorithm(ES384)
// The decoder should support the token type: Access Token + JWT.
.jwtProcessorCustomizer(customizer -> customizer.setJWSTypeVerifier(
new DefaultJOSEObjectTypeVerifier<SecurityContext>(new JOSEObjectType("at+jwt"))))
@ -194,14 +202,17 @@ public class SecurityConfiguration {
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).cors().and()
.authorizeRequests(customizer -> customizer
// Only authenticated requests can access your protected APIs
.mvcMatchers("/", "/secret").authenticated()
// Anyone can access the public profile.
.mvcMatchers("/profile").permitAll()
);
public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults()))
.authorizeHttpRequests(requests -> requests
// Allow all requests to the public APIs.
.requestMatchers("/api/.wellknown/**").permitAll()
// Require jwt token validation for the protected APIs.
.anyRequest().authenticated());
return http.build();
}
}
@ -226,20 +237,14 @@ import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "*")
@RestController
public class ProtectedController {
@GetMapping("/")
public String protectedRoot() {
return "Protected root.";
@GetMapping("/api/profile")
public String protectedProfile() {
return "Protected profile.";
}
@GetMapping("/secret")
public String protectedSecret() {
return "Protected secret.";
}
@GetMapping("/profile")
public String publicProfile() {
return "Public profile.";
@GetMapping("/api/.wellknown/config.json")
public String publicConfig() {
return "Public config.";
}
}
```
@ -274,7 +279,7 @@ Request your protected API with the Access Token as the Bearer token in the Auth
<pre>
<code className="language-bash">
{`curl --include '${appendPath(props.endpoint, '/secret')}' \\
{`curl --include '${appendPath(props.endpoint, '/api/profile')}' \\
--header 'Authorization: Bearer <your-access-token>'`}
</code>
</pre>