SecurityFilterChain
Auth| Authentication | Authorization |
Auth| Uwierzytelnianie | Autoryzacja |
📚 Explore the full series:1️⃣ Introduction 📝
+--------+ +---------------+ | |--(1)- Authorization Request ->| Resource | | | | Owner użytkownik | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| Authorization | | Client frontend| | Server Okta | | |<-(4)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(5)----- Access Token ------>| Resource | | | | Serverbackend | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
@Bean
@Order(0)
SecurityFilterChain oidcProtocolEndpointsChain(
HttpSecurity http,
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator
) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(withDefaults())
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.accessTokenRequestConverter(new OAuth2PasswordGrantAuthenticationConverter())
.authenticationProvider(new OAuth2PasswordGrantAuthenticationProvider(userDetailsService, passwordEncoder, authorizationService, tokenGenerator)));
return http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta"))
).build();
}
Configurer, Customizer
@Bean
@Order(2)
SecurityFilterChain oauthChain(
HttpSecurity http,
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> permissionsCustomizer
) throws Exception {
return http
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()))
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(permissionsCustomizer)
.build();
}
+--------+ +---------------+ | |--(1)- Authorization Request ->| | | | | użytkownik | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| | |frontend| | Okta | | |<-(4)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(5)----- Access Token ------>| | | | | backend | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
</dependency>
Okta Developer Edition (forever-free)
⇒ Integrator Free Plan
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>${okta.springboot.version}</version>
</dependency>
Okta Developer Edition (forever-free)
⇒ Integrator Free Plan
org.springframework.security.oauth2.server.resource.InvalidBearerTokenException:
An error occurred while attempting to decode the Jwt: JOSE header typ (type) application/okta-internal-at+jwt not allowed
Forum
Okta docsOrg authorization server
Every Okta org comes equipped with a built-in org authorization server. The org authorization server issues access tokens for accessing Okta resources in your Okta org domain. Use the org authorization server to perform Single Sign-On (SSO) with Okta for your OIDC apps or to get an access token to access Okta APIs.Note: In the org authorization server, Okta is the resource server.(...)
Custom authorization server
(...)
Default custom authorization server
(...)
https://{yourOktaDomain}.okta.com/.well-known/openid-configuration
https://{yourOktaDomain}.okta.com/oauth2/default/.well-known/openid-configuration
+--------+ +---------------+ | |--(1)- Authorization Request ->| | | | | użytkownik | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| | |frontend| | Okta | | |<-(4)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(5)----- Access Token ------>| | | | | backend | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
scope?scope pyta
(opcjonalnie)
scope = co wolno z tym tokenem (gruboziarniste)
admin.permissions:
resources: asset, promotion, collection
operations: view, modify, publish
metapermissions: role.delete, role.modify, role.view
@ConfigurationProperties("admin.permissions")
class Permissions {
void authorize(HttpSecurity http) {
resources.forEach(resource -> operations.forEach(operation ->
() -> http.authorizeHttpRequests(operation.on(resource))));
}
enum Operations {
view(GET), modify(POST, PATCH, PUT), publish("/publication/**", PATCH);
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> on(String resource) {
return authorize -> matchingMethods.forEach(httpMethod ->
authorize.requestMatchers(httpMethod, resource + "/**" + additionalPath)
.access(hasAuthority(resource + "." + name())));
}
private final Set<HttpMethod> matchingMethods;
private final String additionalPath;
}
}
application-security.yaml (protip)
@Configuration(proxyBeanMethods = false)
class SecurityConfiguration {
@Profile({"!security"})
@Bean
SecurityFilterChain noAuthenticationFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable);
.authorizeHttpRequests((request) ->
request.anyRequest().permitAll());
.build();
}
@Profile({"security"})
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(BasicAuthSettings.class)
static class SecurityTurnedOn {
// all other filter chains here
}
}
+--------+ +---------------+ | |--(1)- Authorization Request ->| | | | | użytkownik | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| | |frontend| | Okta backend | | |<-(4)----- Access Token -------| | | | +---------------++ + | | | backend | | | +---------------++ + | |--(5)----- Access Token ------>| | | | | backend | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
// DisabledException, LockedException, BadCredentialsException
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
@Bean
@Order(0)
SecurityFilterChain oidcProtocolEndpointsChain(
HttpSecurity http,
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator
) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(withDefaults())
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.accessTokenRequestConverter(new OAuth2PasswordGrantAuthenticationConverter())
.authenticationProvider(new OAuth2PasswordGrantAuthenticationProvider(userDetailsService, passwordEncoder, authorizationService, tokenGenerator)));
return http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta"))
).build();
}
/**
* ...
* @throws UsernameNotFoundException if the user
* could not be found or the user has no
* GrantedAuthority
*/
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException;
}
+--------+ +---------------+ | |--(1)- Authorization Request ->| | | | | użytkownik | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| | |frontend| | | | |<-(4)----- Access Token -------| | | | + + | | | backend | | | + + | |--(5)----- Access Token ------>| | | | | | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
+----------+
| Resource |
| Owner |
+----------+
^
|
|
+------|----+ Client Identifier +---------------+
| .---+---------(1)-- & Redirect URI ------->| |
| | | | | |
| | '----|-----(2)-- User authenticates --->| |
| | User- | | Authorization |
| | Agent | | Server |
| | | | |
| | .---|-----(3)-- Authorization Code ---<| |
+-|-----|---+ +---------------+
| | ^ v
| | | |
^ v | |
+---------+ | |
| |>---(4)-- Authorization Code ---------' |
| Client | & Redirect URI |
| | |
| |<---(5)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
- [przekierowanie] PKCE (code challenge)
- [przekierowanie] Jednorazowy kod
- PKCE (code verifier)
@Bean
@Order(0)
SecurityFilterChain oidcProtocolEndpointsChain(
HttpSecurity http,
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator
) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(withDefaults())
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.accessTokenRequestConverter(new OAuth2PasswordGrantAuthenticationConverter())
.authenticationProvider(new OAuth2PasswordGrantAuthenticationProvider(userDetailsService, passwordEncoder, authorizationService, tokenGenerator)));
return http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/okta"))
).build();
}
@Bean
@Order(1)
SecurityFilterChain oktaLoginChain(HttpSecurity http, UserService userService) throws Exception {
return http
.securityMatcher(anyOf(antMatcher("/oauth2/**"), antMatcher("/login/oauth2/**")))
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2Login(oauth2Login -> oauth2Login.successHandler(new CreateUserHandler(userService, requestCache)))
.build();
}
spring.security:
oauth2:
authorizationserver:
client:
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true
spring.security:
oauth2:
authorizationserver:
# how external apps connect this
client:
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true
+----------+
| Resource |
| Owner |
+----------+
^
|
|
+-----|----+ Client Identifier +---------------+
| .---+---------(1)-- & Redirect URI ------->| |
| | | | | |
| | '---------(2)-- User authenticates --->| |
| | User- | | Authorization |
| | Agent | | Server |
| | | | |
| | .--------(3)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
| | | |
^ v | |
+---------+ | |
| |>---(4)-- Authorization Code ---------' |
| Client | & Redirect URI |
| | |
| |<---(5)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
+----------+
| Resource |
| Owner |
+----------+
^
|
|
+-----|----+ Client Identifier +---------------+
| .---+---------(1)-- & Redirect URI ------->| |
| | | | | |
| | '---------(2)-- User authenticates --->| |
| | User- | | Authorization |
| | Agent | | Server |
| | | | |
| | .--------(3)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
| | | |
^ v | |
+---------+ | |
| |>---(4)-- Authorization Code ---------' |
| Client | & Redirect URI |
| | |
| |<---(5)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
+----------+ +---------------+
| Resource | | |
| Owner | | |
+----------+ .-(2.5)- OAuth flow ->| Okta |
^ | | Authorization |
| | | Server |
| ^ | |
+-----|----+ Client Identifier +---------------+ | |
| .---+---------(1)-- & Redirect URI ------->| | +---------------+
| | | | | | v
| | '---------(2)-- User authenticates --->| | |
| | User- | | Authorization | |
| | Agent | | Server |<-(2¾)- OAuth flow -'
| | | | |
| | .--------(3)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
| | | |
^ v | |
+---------+ | |
| |>---(4)-- Authorization Code ---------' |
| Client | & Redirect URI |
| | |
| |<---(5)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
spring.security:
oauth2:
authorizationserver:
# how external apps connect this
client:
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true
spring.security:
oauth2:
# how this connects Okta
client:
registration:
okta:
provider: okta
client-id: ${app.security.okta.client-id}
client-secret: ${app.security.okta.client-secret}
scope:
- openid
- profile
- email
- offline_access
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
user-info-uri: ${okta.oauth2.issuer}/v1/userinfo
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
user-name-attribute: sub
authorizationserver:
# how external apps connect this
client:
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true
JwtDecoder i JwtEncoder
springdoc-openapi)
@SecurityScheme(
name = "openid-connect",
type = SecuritySchemeType.OPENIDCONNECT,
openIdConnectUrl = "/api/.well-known/openid-configuration")
@SecurityScheme(
name = "bearer-jwt",
type = SecuritySchemeType.HTTP,
description = "Fallback method - paste any access token you obtained",
scheme = "bearer",
bearerFormat = "JWT")
@OpenAPIDefinition(
info = @Info(
title = "${app.title}",
description = "${app.description}",
version = "${app.version}"),
security = {
@SecurityRequirement(name = "openid-connect", scopes = "openid"),
@SecurityRequirement(name = "bearer-jwt")})
@Configuration(proxyBeanMethods = false)
class SecurityOpenAPI {}
spring.security:
oauth2:
# how this connects Okta
client:
registration:
okta:
provider: okta
client-id: ${app.security.okta.client-id}
client-secret: ${app.security.okta.client-secret}
scope:
- openid
- profile
- email
- offline_access
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
user-info-uri: ${okta.oauth2.issuer}/v1/userinfo
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
user-name-attribute: sub
authorizationserver:
# how external apps connect this
client:
reco:
registration:
client-id: ${app.security.reco.client-id}
client-secret: "{noop}${app.security.reco.client-secret}"
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- client_credentials
scopes:
- openid
- offline_access
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true
spring.security:
oauth2:
# how this connects Okta
client:
registration:
okta:
provider: okta
client-id: ${app.security.okta.client-id}
client-secret: ${app.security.okta.client-secret}
scope:
- openid
- profile
- email
- offline_access
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
user-info-uri: ${okta.oauth2.issuer}/v1/userinfo
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
user-name-attribute: sub
authorizationserver:
# how external apps connect this
client:
reco:
registration:
client-id: ${app.security.reco.client-id}
client-secret: "{noop}${app.security.reco.client-secret}"
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- client_credentials
scopes:
- openid
- offline_access
frontend:
registration:
client-id: ${app.security.frontend.client-id}
client-secret: "{noop}${app.security.frontend.client-secret}"
client-authentication-methods:
- client_secret_basic
- client_secret_post
authorization-grant-types:
- authorization_code
- refresh_token
- password
redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
- http://localhost:8080/api/swagger-ui/oauth2-redirect.html
post-logout-redirect-uris:
- ${app.frontend.url}/api/auth/callback/backend
scopes:
- openid
- profile
- email
- offline_access
token:
access-token-time-to-live: P6D
refresh-token-time-to-live: P6D
require-proof-key: true