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, 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. 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) 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) and [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture)
for more details. for more details.
</InlineNotification> </InlineNotification>
</Step> </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`). 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> <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> </p>
An example of the response: An example of the response:
<pre> <pre>
<code className="language-json"> <code className="language-json">
{`{ {`{
// ... // ...
"issuer": "${appendPath(props.endpoint, '/oidc')}", "issuer": "${appendPath(props.endpoint, '/oidc')}",
"jwks_uri": "${appendPath(props.endpoint, '/oidc/jwks')}" "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. Use an `application.yml` file (instead of the default `application.properties`) to configure the server port, audience, and OAuth2 resource server.
<pre> <pre>
<code className="language-yaml"> <code className="language-yaml">
{`# path/to/project/src/main/resources/application.yaml {`# path/to/project/src/main/resources/application.yaml
server: server:
port: 3000 port: 3000
@ -89,7 +92,7 @@ spring:
jwt: jwt:
issuer-uri: ${appendPath(props.endpoint, '/oidc')} issuer-uri: ${appendPath(props.endpoint, '/oidc')}
jwk-set-uri: ${appendPath(props.endpoint, '/oidc/jwks')}`} jwk-set-uri: ${appendPath(props.endpoint, '/oidc/jwks')}`}
</code> </code>
</pre> </pre>
- `audience`: The unique API identifier of your protected API resource. - `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> { public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final OAuth2Error oAuth2Error = new OAuth2Error("invalid_token", "Required audience not found", null);
private final String audience; private final String audience;
public AudienceValidator(String audience) { public AudienceValidator(String audience) {
@ -124,18 +125,21 @@ public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
@Override @Override
public OAuth2TokenValidatorResult validate(Jwt jwt) { public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (!jwt.getAudience().contains(audience)) { 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(); 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>
<Step title="Configure Spring Security"> <Step title="Configure Spring Security">
@ -154,17 +158,19 @@ import com.nimbusds.jose.proc.SecurityContext;
import io.logto.springboot.sample.validator.AudienceValidator; import io.logto.springboot.sample.validator.AudienceValidator;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 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.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; 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.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 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 @EnableWebSecurity
public class SecurityConfiguration { public class SecurityConfiguration {
@ -180,6 +186,8 @@ public class SecurityConfiguration {
@Bean @Bean
public JwtDecoder jwtDecoder() { public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwksUri) 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. // The decoder should support the token type: Access Token + JWT.
.jwtProcessorCustomizer(customizer -> customizer.setJWSTypeVerifier( .jwtProcessorCustomizer(customizer -> customizer.setJWSTypeVerifier(
new DefaultJOSEObjectTypeVerifier<SecurityContext>(new JOSEObjectType("at+jwt")))) new DefaultJOSEObjectTypeVerifier<SecurityContext>(new JOSEObjectType("at+jwt"))))
@ -194,14 +202,17 @@ public class SecurityConfiguration {
} }
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).cors().and() http
.authorizeRequests(customizer -> customizer .securityMatcher("/api/**")
// Only authenticated requests can access your protected APIs .oauth2ResourceServer(oauth2 -> oauth2
.mvcMatchers("/", "/secret").authenticated() .jwt(Customizer.withDefaults()))
// Anyone can access the public profile. .authorizeHttpRequests(requests -> requests
.mvcMatchers("/profile").permitAll() // Allow all requests to the public APIs.
); .requestMatchers("/api/.wellknown/**").permitAll()
// Require jwt token validation for the protected APIs.
.anyRequest().authenticated());
return http.build(); return http.build();
} }
} }
@ -226,20 +237,14 @@ import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "*") @CrossOrigin(origins = "*")
@RestController @RestController
public class ProtectedController { public class ProtectedController {
@GetMapping("/api/profile")
@GetMapping("/") public String protectedProfile() {
public String protectedRoot() { return "Protected profile.";
return "Protected root.";
} }
@GetMapping("/secret") @GetMapping("/api/.wellknown/config.json")
public String protectedSecret() { public String publicConfig() {
return "Protected secret."; return "Public config.";
}
@GetMapping("/profile")
public String publicProfile() {
return "Public profile.";
} }
} }
``` ```
@ -274,7 +279,7 @@ Request your protected API with the Access Token as the Bearer token in the Auth
<pre> <pre>
<code className="language-bash"> <code className="language-bash">
{`curl --include '${appendPath(props.endpoint, '/secret')}' \\ {`curl --include '${appendPath(props.endpoint, '/api/profile')}' \\
--header 'Authorization: Bearer <your-access-token>'`} --header 'Authorization: Bearer <your-access-token>'`}
</code> </code>
</pre> </pre>