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:
parent
dc6fbe212e
commit
a02c20a664
1 changed files with 43 additions and 38 deletions
|
@ -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>
|
||||||
|
|
Loading…
Add table
Reference in a new issue