Migrate docs to Antora

Issue gh-1295
This commit is contained in:
Rob Winch
2023-07-03 13:36:26 -05:00
committed by Steve Riesenberg
parent ec3d4fa7e1
commit 0f0424ad2a
62 changed files with 327 additions and 198 deletions

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.extgrant;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
public class CustomCodeGrantAuthenticationConverter implements AuthenticationConverter {
@Nullable
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!"urn:ietf:params:oauth:grant-type:custom_code".equals(grantType)) { // <1>
return null;
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = getParameters(request);
// code (REQUIRED)
String code = parameters.getFirst(OAuth2ParameterNames.CODE); // <2>
if (!StringUtils.hasText(code) ||
parameters.get(OAuth2ParameterNames.CODE).size() != 1) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
}
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
!key.equals(OAuth2ParameterNames.CODE)) {
additionalParameters.put(key, value.get(0));
}
});
return new CustomCodeGrantAuthenticationToken(code, clientPrincipal, additionalParameters); // <3>
}
// @fold:on
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
for (String value : values) {
parameters.add(key, value);
}
}
});
return parameters;
}
// @fold:off
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.extgrant;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
public class CustomCodeGrantAuthenticationProvider implements AuthenticationProvider {
// @fold:on
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
public CustomCodeGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
}
// @fold:off
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomCodeGrantAuthenticationToken customCodeGrantAuthentication =
(CustomCodeGrantAuthenticationToken) authentication;
// Ensure the client is authenticated
OAuth2ClientAuthenticationToken clientPrincipal =
getAuthenticatedClientElseThrowInvalidClient(customCodeGrantAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// Ensure the client is configured to use this authorization grant type
if (!registeredClient.getAuthorizationGrantTypes().contains(customCodeGrantAuthentication.getGrantType())) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
// TODO Validate the code parameter
// Generate the access token
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(clientPrincipal)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(customCodeGrantAuthentication.getGrantType())
.authorizationGrant(customCodeGrantAuthentication)
.build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", null);
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), null);
// Initialize the OAuth2Authorization
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(clientPrincipal.getName())
.authorizationGrantType(customCodeGrantAuthentication.getGrantType());
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(
OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
((ClaimAccessor) generatedAccessToken).getClaims())
);
} else {
authorizationBuilder.accessToken(accessToken);
}
OAuth2Authorization authorization = authorizationBuilder.build();
// Save the OAuth2Authorization
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
}
@Override
public boolean supports(Class<?> authentication) {
return CustomCodeGrantAuthenticationToken.class.isAssignableFrom(authentication);
}
// @fold:on
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
// @fold:off
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.extgrant;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.util.Assert;
public class CustomCodeGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private final String code;
public CustomCodeGrantAuthenticationToken(String code, Authentication clientPrincipal,
@Nullable Map<String, Object> additionalParameters) {
super(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:custom_code"),
clientPrincipal, additionalParameters);
Assert.hasText(code, "code cannot be empty");
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.extgrant;
import java.util.UUID;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// @formatter:off
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(
HttpSecurity http,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<?> tokenGenerator) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter( // <1>
new CustomCodeGrantAuthenticationConverter())
.authenticationProvider( // <2>
new CustomCodeGrantAuthenticationProvider(
authorizationService, tokenGenerator)));
// @fold:on
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http
.securityMatcher(endpointsMatcher)
.authorizeHttpRequests(authorize ->
authorize
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
// @fold:off
return http.build();
}
// @formatter:on
// @fold:on
// @formatter:off
@Bean
RegisteredClientRepository registeredClientRepository() {
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:custom_code"))
.scope("message.read")
.scope("message.write")
.build();
return new InMemoryRegisteredClientRepository(messagingClient);
}
// @formatter:on
@Bean
OAuth2AuthorizationService authorizationService() {
return new InMemoryOAuth2AuthorizationService();
}
@Bean
OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
// @fold:off
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.gettingstarted;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean // <2>
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean // <3>
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean // <4>
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oidc-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/oidc-client")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(oidcClient);
}
@Bean // <5>
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() { // <6>
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean // <7>
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean // <8>
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}

View File

@@ -0,0 +1,29 @@
server:
port: 9000
logging:
level:
org.springframework.security: trace
spring:
security:
oauth2:
authorizationserver:
client:
oidc-client:
registration:
client-id: "oidc-client"
client-secret: "{noop}secret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
redirect-uris:
- "http://127.0.0.1:8080/login/oauth2/code/oidc-client"
post-logout-redirect-uris:
- "http://127.0.0.1:8080/"
scopes:
- "openid"
- "profile"
require-authorization-consent: true

View File

@@ -0,0 +1,360 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.authorization;
import java.time.Instant;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "`authorization`")
public class Authorization {
@Id
@Column
private String id;
private String registeredClientId;
private String principalName;
private String authorizationGrantType;
@Column(length = 1000)
private String authorizedScopes;
@Column(length = 4000)
private String attributes;
@Column(length = 500)
private String state;
@Column(length = 4000)
private String authorizationCodeValue;
private Instant authorizationCodeIssuedAt;
private Instant authorizationCodeExpiresAt;
private String authorizationCodeMetadata;
@Column(length = 4000)
private String accessTokenValue;
private Instant accessTokenIssuedAt;
private Instant accessTokenExpiresAt;
@Column(length = 2000)
private String accessTokenMetadata;
private String accessTokenType;
@Column(length = 1000)
private String accessTokenScopes;
@Column(length = 4000)
private String refreshTokenValue;
private Instant refreshTokenIssuedAt;
private Instant refreshTokenExpiresAt;
@Column(length = 2000)
private String refreshTokenMetadata;
@Column(length = 4000)
private String oidcIdTokenValue;
private Instant oidcIdTokenIssuedAt;
private Instant oidcIdTokenExpiresAt;
@Column(length = 2000)
private String oidcIdTokenMetadata;
@Column(length = 2000)
private String oidcIdTokenClaims;
@Column(length = 4000)
private String userCodeValue;
private Instant userCodeIssuedAt;
private Instant userCodeExpiresAt;
@Column(length = 2000)
private String userCodeMetadata;
@Column(length = 4000)
private String deviceCodeValue;
private Instant deviceCodeIssuedAt;
private Instant deviceCodeExpiresAt;
@Column(length = 2000)
private String deviceCodeMetadata;
// @fold:on
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
public String getAuthorizationGrantType() {
return authorizationGrantType;
}
public void setAuthorizationGrantType(String authorizationGrantType) {
this.authorizationGrantType = authorizationGrantType;
}
public String getAuthorizedScopes() {
return this.authorizedScopes;
}
public void setAuthorizedScopes(String authorizedScopes) {
this.authorizedScopes = authorizedScopes;
}
public String getAttributes() {
return attributes;
}
public void setAttributes(String attributes) {
this.attributes = attributes;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getAuthorizationCodeValue() {
return authorizationCodeValue;
}
public void setAuthorizationCodeValue(String authorizationCode) {
this.authorizationCodeValue = authorizationCode;
}
public Instant getAuthorizationCodeIssuedAt() {
return authorizationCodeIssuedAt;
}
public void setAuthorizationCodeIssuedAt(Instant authorizationCodeIssuedAt) {
this.authorizationCodeIssuedAt = authorizationCodeIssuedAt;
}
public Instant getAuthorizationCodeExpiresAt() {
return authorizationCodeExpiresAt;
}
public void setAuthorizationCodeExpiresAt(Instant authorizationCodeExpiresAt) {
this.authorizationCodeExpiresAt = authorizationCodeExpiresAt;
}
public String getAuthorizationCodeMetadata() {
return authorizationCodeMetadata;
}
public void setAuthorizationCodeMetadata(String authorizationCodeMetadata) {
this.authorizationCodeMetadata = authorizationCodeMetadata;
}
public String getAccessTokenValue() {
return accessTokenValue;
}
public void setAccessTokenValue(String accessToken) {
this.accessTokenValue = accessToken;
}
public Instant getAccessTokenIssuedAt() {
return accessTokenIssuedAt;
}
public void setAccessTokenIssuedAt(Instant accessTokenIssuedAt) {
this.accessTokenIssuedAt = accessTokenIssuedAt;
}
public Instant getAccessTokenExpiresAt() {
return accessTokenExpiresAt;
}
public void setAccessTokenExpiresAt(Instant accessTokenExpiresAt) {
this.accessTokenExpiresAt = accessTokenExpiresAt;
}
public String getAccessTokenMetadata() {
return accessTokenMetadata;
}
public void setAccessTokenMetadata(String accessTokenMetadata) {
this.accessTokenMetadata = accessTokenMetadata;
}
public String getAccessTokenType() {
return accessTokenType;
}
public void setAccessTokenType(String accessTokenType) {
this.accessTokenType = accessTokenType;
}
public String getAccessTokenScopes() {
return accessTokenScopes;
}
public void setAccessTokenScopes(String accessTokenScopes) {
this.accessTokenScopes = accessTokenScopes;
}
public String getRefreshTokenValue() {
return refreshTokenValue;
}
public void setRefreshTokenValue(String refreshToken) {
this.refreshTokenValue = refreshToken;
}
public Instant getRefreshTokenIssuedAt() {
return refreshTokenIssuedAt;
}
public void setRefreshTokenIssuedAt(Instant refreshTokenIssuedAt) {
this.refreshTokenIssuedAt = refreshTokenIssuedAt;
}
public Instant getRefreshTokenExpiresAt() {
return refreshTokenExpiresAt;
}
public void setRefreshTokenExpiresAt(Instant refreshTokenExpiresAt) {
this.refreshTokenExpiresAt = refreshTokenExpiresAt;
}
public String getRefreshTokenMetadata() {
return refreshTokenMetadata;
}
public void setRefreshTokenMetadata(String refreshTokenMetadata) {
this.refreshTokenMetadata = refreshTokenMetadata;
}
public String getOidcIdTokenValue() {
return oidcIdTokenValue;
}
public void setOidcIdTokenValue(String idToken) {
this.oidcIdTokenValue = idToken;
}
public Instant getOidcIdTokenIssuedAt() {
return oidcIdTokenIssuedAt;
}
public void setOidcIdTokenIssuedAt(Instant idTokenIssuedAt) {
this.oidcIdTokenIssuedAt = idTokenIssuedAt;
}
public Instant getOidcIdTokenExpiresAt() {
return oidcIdTokenExpiresAt;
}
public void setOidcIdTokenExpiresAt(Instant idTokenExpiresAt) {
this.oidcIdTokenExpiresAt = idTokenExpiresAt;
}
public String getOidcIdTokenMetadata() {
return oidcIdTokenMetadata;
}
public void setOidcIdTokenMetadata(String idTokenMetadata) {
this.oidcIdTokenMetadata = idTokenMetadata;
}
public String getOidcIdTokenClaims() {
return oidcIdTokenClaims;
}
public void setOidcIdTokenClaims(String idTokenClaims) {
this.oidcIdTokenClaims = idTokenClaims;
}
public String getUserCodeValue() {
return this.userCodeValue;
}
public void setUserCodeValue(String userCodeValue) {
this.userCodeValue = userCodeValue;
}
public Instant getUserCodeIssuedAt() {
return this.userCodeIssuedAt;
}
public void setUserCodeIssuedAt(Instant userCodeIssuedAt) {
this.userCodeIssuedAt = userCodeIssuedAt;
}
public Instant getUserCodeExpiresAt() {
return this.userCodeExpiresAt;
}
public void setUserCodeExpiresAt(Instant userCodeExpiresAt) {
this.userCodeExpiresAt = userCodeExpiresAt;
}
public String getUserCodeMetadata() {
return this.userCodeMetadata;
}
public void setUserCodeMetadata(String userCodeMetadata) {
this.userCodeMetadata = userCodeMetadata;
}
public String getDeviceCodeValue() {
return this.deviceCodeValue;
}
public void setDeviceCodeValue(String deviceCodeValue) {
this.deviceCodeValue = deviceCodeValue;
}
public Instant getDeviceCodeIssuedAt() {
return this.deviceCodeIssuedAt;
}
public void setDeviceCodeIssuedAt(Instant deviceCodeIssuedAt) {
this.deviceCodeIssuedAt = deviceCodeIssuedAt;
}
public Instant getDeviceCodeExpiresAt() {
return this.deviceCodeExpiresAt;
}
public void setDeviceCodeExpiresAt(Instant deviceCodeExpiresAt) {
this.deviceCodeExpiresAt = deviceCodeExpiresAt;
}
public String getDeviceCodeMetadata() {
return this.deviceCodeMetadata;
}
public void setDeviceCodeMetadata(String deviceCodeMetadata) {
this.deviceCodeMetadata = deviceCodeMetadata;
}
// @fold:off
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.authorizationConsent;
import java.io.Serializable;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
@Entity
@Table(name = "`authorizationConsent`")
@IdClass(AuthorizationConsent.AuthorizationConsentId.class)
public class AuthorizationConsent {
@Id
private String registeredClientId;
@Id
private String principalName;
@Column(length = 1000)
private String authorities;
// @fold:on
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
public String getAuthorities() {
return authorities;
}
public void setAuthorities(String authorities) {
this.authorities = authorities;
}
// @fold:off
public static class AuthorizationConsentId implements Serializable {
private String registeredClientId;
private String principalName;
// @fold:on
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
// @fold:off
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorizationConsentId that = (AuthorizationConsentId) o;
return registeredClientId.equals(that.registeredClientId) && principalName.equals(that.principalName);
}
@Override
public int hashCode() {
return Objects.hash(registeredClientId, principalName);
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.client;
import java.time.Instant;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "`client`")
public class Client {
@Id
private String id;
private String clientId;
private Instant clientIdIssuedAt;
private String clientSecret;
private Instant clientSecretExpiresAt;
private String clientName;
@Column(length = 1000)
private String clientAuthenticationMethods;
@Column(length = 1000)
private String authorizationGrantTypes;
@Column(length = 1000)
private String redirectUris;
@Column(length = 1000)
private String postLogoutRedirectUris;
@Column(length = 1000)
private String scopes;
@Column(length = 2000)
private String clientSettings;
@Column(length = 2000)
private String tokenSettings;
// @fold:on
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public Instant getClientIdIssuedAt() {
return clientIdIssuedAt;
}
public void setClientIdIssuedAt(Instant clientIdIssuedAt) {
this.clientIdIssuedAt = clientIdIssuedAt;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public Instant getClientSecretExpiresAt() {
return clientSecretExpiresAt;
}
public void setClientSecretExpiresAt(Instant clientSecretExpiresAt) {
this.clientSecretExpiresAt = clientSecretExpiresAt;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getClientAuthenticationMethods() {
return clientAuthenticationMethods;
}
public void setClientAuthenticationMethods(String clientAuthenticationMethods) {
this.clientAuthenticationMethods = clientAuthenticationMethods;
}
public String getAuthorizationGrantTypes() {
return authorizationGrantTypes;
}
public void setAuthorizationGrantTypes(String authorizationGrantTypes) {
this.authorizationGrantTypes = authorizationGrantTypes;
}
public String getRedirectUris() {
return redirectUris;
}
public void setRedirectUris(String redirectUris) {
this.redirectUris = redirectUris;
}
public String getPostLogoutRedirectUris() {
return this.postLogoutRedirectUris;
}
public void setPostLogoutRedirectUris(String postLogoutRedirectUris) {
this.postLogoutRedirectUris = postLogoutRedirectUris;
}
public String getScopes() {
return scopes;
}
public void setScopes(String scopes) {
this.scopes = scopes;
}
public String getClientSettings() {
return clientSettings;
}
public void setClientSettings(String clientSettings) {
this.clientSettings = clientSettings;
}
public String getTokenSettings() {
return tokenSettings;
}
public void setTokenSettings(String tokenSettings) {
this.tokenSettings = tokenSettings;
}
// @fold:off
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.authorization;
import java.util.Optional;
import sample.jpa.entity.authorization.Authorization;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorizationRepository extends JpaRepository<Authorization, String> {
Optional<Authorization> findByState(String state);
Optional<Authorization> findByAuthorizationCodeValue(String authorizationCode);
Optional<Authorization> findByAccessTokenValue(String accessToken);
Optional<Authorization> findByRefreshTokenValue(String refreshToken);
Optional<Authorization> findByOidcIdTokenValue(String idToken);
Optional<Authorization> findByUserCodeValue(String userCode);
Optional<Authorization> findByDeviceCodeValue(String deviceCode);
@Query("select a from Authorization a where a.state = :token" +
" or a.authorizationCodeValue = :token" +
" or a.accessTokenValue = :token" +
" or a.refreshTokenValue = :token" +
" or a.oidcIdTokenValue = :token" +
" or a.userCodeValue = :token" +
" or a.deviceCodeValue = :token"
)
Optional<Authorization> findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValueOrUserCodeValueOrDeviceCodeValue(@Param("token") String token);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.authorizationConsent;
import java.util.Optional;
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorizationConsentRepository extends JpaRepository<AuthorizationConsent, AuthorizationConsent.AuthorizationConsentId> {
Optional<AuthorizationConsent> findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.client;
import java.util.Optional;
import sample.jpa.entity.client.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClientRepository extends JpaRepository<Client, String> {
Optional<Client> findByClientId(String clientId);
}

View File

@@ -0,0 +1,310 @@
/*
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.authorization;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.jpa.entity.authorization.Authorization;
import sample.jpa.repository.authorization.AuthorizationRepository;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService {
private final AuthorizationRepository authorizationRepository;
private final RegisteredClientRepository registeredClientRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationRepository, RegisteredClientRepository registeredClientRepository) {
Assert.notNull(authorizationRepository, "authorizationRepository cannot be null");
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.authorizationRepository = authorizationRepository;
this.registeredClientRepository = registeredClientRepository;
ClassLoader classLoader = JpaOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
this.authorizationRepository.save(toEntity(authorization));
}
@Override
public void remove(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
this.authorizationRepository.deleteById(authorization.getId());
}
@Override
public OAuth2Authorization findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.authorizationRepository.findById(id).map(this::toObject).orElse(null);
}
@Override
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
Assert.hasText(token, "token cannot be empty");
Optional<Authorization> result;
if (tokenType == null) {
result = this.authorizationRepository.findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValueOrUserCodeValueOrDeviceCodeValue(token);
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByState(token);
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByAuthorizationCodeValue(token);
} else if (OAuth2ParameterNames.ACCESS_TOKEN.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByAccessTokenValue(token);
} else if (OAuth2ParameterNames.REFRESH_TOKEN.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByRefreshTokenValue(token);
} else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByOidcIdTokenValue(token);
} else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByUserCodeValue(token);
} else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByDeviceCodeValue(token);
} else {
result = Optional.empty();
}
return result.map(this::toObject).orElse(null);
}
private OAuth2Authorization toObject(Authorization entity) {
RegisteredClient registeredClient = this.registeredClientRepository.findById(entity.getRegisteredClientId());
if (registeredClient == null) {
throw new DataRetrievalFailureException(
"The RegisteredClient with id '" + entity.getRegisteredClientId() + "' was not found in the RegisteredClientRepository.");
}
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
.id(entity.getId())
.principalName(entity.getPrincipalName())
.authorizationGrantType(resolveAuthorizationGrantType(entity.getAuthorizationGrantType()))
.authorizedScopes(StringUtils.commaDelimitedListToSet(entity.getAuthorizedScopes()))
.attributes(attributes -> attributes.putAll(parseMap(entity.getAttributes())));
if (entity.getState() != null) {
builder.attribute(OAuth2ParameterNames.STATE, entity.getState());
}
if (entity.getAuthorizationCodeValue() != null) {
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
entity.getAuthorizationCodeValue(),
entity.getAuthorizationCodeIssuedAt(),
entity.getAuthorizationCodeExpiresAt());
builder.token(authorizationCode, metadata -> metadata.putAll(parseMap(entity.getAuthorizationCodeMetadata())));
}
if (entity.getAccessTokenValue() != null) {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
entity.getAccessTokenValue(),
entity.getAccessTokenIssuedAt(),
entity.getAccessTokenExpiresAt(),
StringUtils.commaDelimitedListToSet(entity.getAccessTokenScopes()));
builder.token(accessToken, metadata -> metadata.putAll(parseMap(entity.getAccessTokenMetadata())));
}
if (entity.getRefreshTokenValue() != null) {
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
entity.getRefreshTokenValue(),
entity.getRefreshTokenIssuedAt(),
entity.getRefreshTokenExpiresAt());
builder.token(refreshToken, metadata -> metadata.putAll(parseMap(entity.getRefreshTokenMetadata())));
}
if (entity.getOidcIdTokenValue() != null) {
OidcIdToken idToken = new OidcIdToken(
entity.getOidcIdTokenValue(),
entity.getOidcIdTokenIssuedAt(),
entity.getOidcIdTokenExpiresAt(),
parseMap(entity.getOidcIdTokenClaims()));
builder.token(idToken, metadata -> metadata.putAll(parseMap(entity.getOidcIdTokenMetadata())));
}
if (entity.getUserCodeValue() != null) {
OAuth2UserCode userCode = new OAuth2UserCode(
entity.getUserCodeValue(),
entity.getUserCodeIssuedAt(),
entity.getUserCodeExpiresAt());
builder.token(userCode, metadata -> metadata.putAll(parseMap(entity.getUserCodeMetadata())));
}
if (entity.getDeviceCodeValue() != null) {
OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(
entity.getDeviceCodeValue(),
entity.getDeviceCodeIssuedAt(),
entity.getDeviceCodeExpiresAt());
builder.token(deviceCode, metadata -> metadata.putAll(parseMap(entity.getDeviceCodeMetadata())));
}
return builder.build();
}
private Authorization toEntity(OAuth2Authorization authorization) {
Authorization entity = new Authorization();
entity.setId(authorization.getId());
entity.setRegisteredClientId(authorization.getRegisteredClientId());
entity.setPrincipalName(authorization.getPrincipalName());
entity.setAuthorizationGrantType(authorization.getAuthorizationGrantType().getValue());
entity.setAuthorizedScopes(StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ","));
entity.setAttributes(writeMap(authorization.getAttributes()));
entity.setState(authorization.getAttribute(OAuth2ParameterNames.STATE));
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
setTokenValues(
authorizationCode,
entity::setAuthorizationCodeValue,
entity::setAuthorizationCodeIssuedAt,
entity::setAuthorizationCodeExpiresAt,
entity::setAuthorizationCodeMetadata
);
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
authorization.getToken(OAuth2AccessToken.class);
setTokenValues(
accessToken,
entity::setAccessTokenValue,
entity::setAccessTokenIssuedAt,
entity::setAccessTokenExpiresAt,
entity::setAccessTokenMetadata
);
if (accessToken != null && accessToken.getToken().getScopes() != null) {
entity.setAccessTokenScopes(StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ","));
}
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken =
authorization.getToken(OAuth2RefreshToken.class);
setTokenValues(
refreshToken,
entity::setRefreshTokenValue,
entity::setRefreshTokenIssuedAt,
entity::setRefreshTokenExpiresAt,
entity::setRefreshTokenMetadata
);
OAuth2Authorization.Token<OidcIdToken> oidcIdToken =
authorization.getToken(OidcIdToken.class);
setTokenValues(
oidcIdToken,
entity::setOidcIdTokenValue,
entity::setOidcIdTokenIssuedAt,
entity::setOidcIdTokenExpiresAt,
entity::setOidcIdTokenMetadata
);
if (oidcIdToken != null) {
entity.setOidcIdTokenClaims(writeMap(oidcIdToken.getClaims()));
}
OAuth2Authorization.Token<OAuth2UserCode> userCode =
authorization.getToken(OAuth2UserCode.class);
setTokenValues(
userCode,
entity::setUserCodeValue,
entity::setUserCodeIssuedAt,
entity::setUserCodeExpiresAt,
entity::setUserCodeMetadata
);
OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode =
authorization.getToken(OAuth2DeviceCode.class);
setTokenValues(
deviceCode,
entity::setDeviceCodeValue,
entity::setDeviceCodeIssuedAt,
entity::setDeviceCodeExpiresAt,
entity::setDeviceCodeMetadata
);
return entity;
}
private void setTokenValues(
OAuth2Authorization.Token<?> token,
Consumer<String> tokenValueConsumer,
Consumer<Instant> issuedAtConsumer,
Consumer<Instant> expiresAtConsumer,
Consumer<String> metadataConsumer) {
if (token != null) {
OAuth2Token oAuth2Token = token.getToken();
tokenValueConsumer.accept(oAuth2Token.getTokenValue());
issuedAtConsumer.accept(oAuth2Token.getIssuedAt());
expiresAtConsumer.accept(oAuth2Token.getExpiresAt());
metadataConsumer.accept(writeMap(token.getMetadata()));
}
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private String writeMap(Map<String, Object> metadata) {
try {
return this.objectMapper.writeValueAsString(metadata);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
} else if (AuthorizationGrantType.DEVICE_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.DEVICE_CODE;
}
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.authorizationConsent;
import java.util.HashSet;
import java.util.Set;
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
import sample.jpa.repository.authorizationConsent.AuthorizationConsentRepository;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
private final AuthorizationConsentRepository authorizationConsentRepository;
private final RegisteredClientRepository registeredClientRepository;
public JpaOAuth2AuthorizationConsentService(AuthorizationConsentRepository authorizationConsentRepository, RegisteredClientRepository registeredClientRepository) {
Assert.notNull(authorizationConsentRepository, "authorizationConsentRepository cannot be null");
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.authorizationConsentRepository = authorizationConsentRepository;
this.registeredClientRepository = registeredClientRepository;
}
@Override
public void save(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
this.authorizationConsentRepository.save(toEntity(authorizationConsent));
}
@Override
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
this.authorizationConsentRepository.deleteByRegisteredClientIdAndPrincipalName(
authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
}
@Override
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
return this.authorizationConsentRepository.findByRegisteredClientIdAndPrincipalName(
registeredClientId, principalName).map(this::toObject).orElse(null);
}
private OAuth2AuthorizationConsent toObject(AuthorizationConsent authorizationConsent) {
String registeredClientId = authorizationConsent.getRegisteredClientId();
RegisteredClient registeredClient = this.registeredClientRepository.findById(registeredClientId);
if (registeredClient == null) {
throw new DataRetrievalFailureException(
"The RegisteredClient with id '" + registeredClientId + "' was not found in the RegisteredClientRepository.");
}
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(
registeredClientId, authorizationConsent.getPrincipalName());
if (authorizationConsent.getAuthorities() != null) {
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsent.getAuthorities())) {
builder.authority(new SimpleGrantedAuthority(authority));
}
}
return builder.build();
}
private AuthorizationConsent toEntity(OAuth2AuthorizationConsent authorizationConsent) {
AuthorizationConsent entity = new AuthorizationConsent();
entity.setRegisteredClientId(authorizationConsent.getRegisteredClientId());
entity.setPrincipalName(authorizationConsent.getPrincipalName());
Set<String> authorities = new HashSet<>();
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
authorities.add(authority.getAuthority());
}
entity.setAuthorities(StringUtils.collectionToCommaDelimitedString(authorities));
return entity;
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.client;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.jpa.entity.client.Client;
import sample.jpa.repository.client.ClientRepository;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaRegisteredClientRepository implements RegisteredClientRepository {
private final ClientRepository clientRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public JpaRegisteredClientRepository(ClientRepository clientRepository) {
Assert.notNull(clientRepository, "clientRepository cannot be null");
this.clientRepository = clientRepository;
ClassLoader classLoader = JpaRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public void save(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
this.clientRepository.save(toEntity(registeredClient));
}
@Override
public RegisteredClient findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.clientRepository.findById(id).map(this::toObject).orElse(null);
}
@Override
public RegisteredClient findByClientId(String clientId) {
Assert.hasText(clientId, "clientId cannot be empty");
return this.clientRepository.findByClientId(clientId).map(this::toObject).orElse(null);
}
private RegisteredClient toObject(Client client) {
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(
client.getClientAuthenticationMethods());
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(
client.getAuthorizationGrantTypes());
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(
client.getRedirectUris());
Set<String> postLogoutRedirectUris = StringUtils.commaDelimitedListToSet(
client.getPostLogoutRedirectUris());
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(
client.getScopes());
RegisteredClient.Builder builder = RegisteredClient.withId(client.getId())
.clientId(client.getClientId())
.clientIdIssuedAt(client.getClientIdIssuedAt())
.clientSecret(client.getClientSecret())
.clientSecretExpiresAt(client.getClientSecretExpiresAt())
.clientName(client.getClientName())
.clientAuthenticationMethods(authenticationMethods ->
clientAuthenticationMethods.forEach(authenticationMethod ->
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
.authorizationGrantTypes((grantTypes) ->
authorizationGrantTypes.forEach(grantType ->
grantTypes.add(resolveAuthorizationGrantType(grantType))))
.redirectUris((uris) -> uris.addAll(redirectUris))
.postLogoutRedirectUris((uris) -> uris.addAll(postLogoutRedirectUris))
.scopes((scopes) -> scopes.addAll(clientScopes));
Map<String, Object> clientSettingsMap = parseMap(client.getClientSettings());
builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());
Map<String, Object> tokenSettingsMap = parseMap(client.getTokenSettings());
builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build());
return builder.build();
}
private Client toEntity(RegisteredClient registeredClient) {
List<String> clientAuthenticationMethods = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
registeredClient.getClientAuthenticationMethods().forEach(clientAuthenticationMethod ->
clientAuthenticationMethods.add(clientAuthenticationMethod.getValue()));
List<String> authorizationGrantTypes = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
authorizationGrantTypes.add(authorizationGrantType.getValue()));
Client entity = new Client();
entity.setId(registeredClient.getId());
entity.setClientId(registeredClient.getClientId());
entity.setClientIdIssuedAt(registeredClient.getClientIdIssuedAt());
entity.setClientSecret(registeredClient.getClientSecret());
entity.setClientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
entity.setClientName(registeredClient.getClientName());
entity.setClientAuthenticationMethods(StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods));
entity.setAuthorizationGrantTypes(StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes));
entity.setRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris()));
entity.setPostLogoutRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getPostLogoutRedirectUris()));
entity.setScopes(StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes()));
entity.setClientSettings(writeMap(registeredClient.getClientSettings().getSettings()));
entity.setTokenSettings(writeMap(registeredClient.getTokenSettings().getSettings()));
return entity;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.NONE;
}
return new ClientAuthenticationMethod(clientAuthenticationMethod); // Custom client authentication method
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pkce;
import java.util.UUID;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
@Configuration
public class ClientConfig {
// tag::client[]
@Bean
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("public-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://127.0.0.1:4200")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(true)
.build()
)
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(publicClient);
}
// end::client[]
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pkce;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
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.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
// @fold:on
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
// @formatter:on
// @fold:off
return http.cors(Customizer.withDefaults()).build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
// @fold:on
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
// @formatter:on
// @fold:off
return http.cors(Customizer.withDefaults()).build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("http://127.0.0.1:4200");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return source;
}
}

View File

@@ -0,0 +1,19 @@
spring:
security:
oauth2:
authorizationserver:
client:
public-client:
registration:
client-id: "public-client"
client-authentication-methods:
- "none"
authorization-grant-types:
- "authorization_code"
redirect-uris:
- "http://127.0.0.1:4200"
scopes:
- "openid"
- "profile"
require-authorization-consent: true
require-proof-key: true

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.sociallogin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
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.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
// Redirect to the OAuth 2.0 Login endpoint when not authenticated
// from the authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor( // <2>
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/my-client"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean // <3>
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// OAuth2 Login handles the redirect to the OAuth 2.0 Login endpoint
// from the authorization server filter chain
.oauth2Login(Customizer.withDefaults()); // <4>
// @formatter:on
return http.build();
}
}

View File

@@ -0,0 +1,23 @@
okta:
base-url: ${OKTA_BASE_URL}
spring:
security:
oauth2:
client:
registration:
my-client:
provider: okta
client-id: ${OKTA_CLIENT_ID}
client-secret: ${OKTA_CLIENT_SECRET}
scope:
- openid
- profile
- email
provider:
okta:
authorization-uri: ${okta.base-url}/oauth2/v1/authorize
token-uri: ${okta.base-url}/oauth2/v1/token
user-info-uri: ${okta.base-url}/oauth2/v1/userinfo
jwk-set-uri: ${okta.base-url}/oauth2/v1/keys
user-name-attribute: sub

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class EnableUserInfoSecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())) // <2>
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
// @formatter:on
return http.build();
}
// @fold:on
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
// @fold:off
@Bean // <3>
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
// @fold:on
@Bean
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.ADDRESS)
.scope(OidcScopes.EMAIL)
.scope(OidcScopes.PHONE)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// @fold:off
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.idtoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@Configuration
public class IdTokenCustomizerConfig {
// @formatter:off
@Bean // <1>
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(
OidcUserInfoService userInfoService) {
return (context) -> {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
OidcUserInfo userInfo = userInfoService.loadUser( // <2>
context.getPrincipal().getName());
context.getClaims().claims(claims ->
claims.putAll(userInfo.getClaims()));
}
};
}
// @formatter:on
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.idtoken;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.stereotype.Service;
/**
* Example service to perform lookup of user info for customizing an {@code id_token}.
*/
@Service
public class OidcUserInfoService {
private final UserInfoRepository userInfoRepository = new UserInfoRepository();
public OidcUserInfo loadUser(String username) {
return new OidcUserInfo(this.userInfoRepository.findByUsername(username));
}
static class UserInfoRepository {
private final Map<String, Map<String, Object>> userInfo = new HashMap<>();
public UserInfoRepository() {
this.userInfo.put("user1", createUser("user1"));
this.userInfo.put("user2", createUser("user2"));
}
public Map<String, Object> findByUsername(String username) {
return this.userInfo.get(username);
}
private static Map<String, Object> createUser(String username) {
return OidcUserInfo.builder()
.subject(username)
.name("First Last")
.givenName("First")
.familyName("Last")
.middleName("Middle")
.nickname("User")
.preferredUsername(username)
.profile("https://example.com/" + username)
.picture("https://example.com/" + username + ".jpg")
.website("https://example.com")
.email(username + "@example.com")
.emailVerified(true)
.gender("female")
.birthdate("1970-01-01")
.zoneinfo("Europe/Paris")
.locale("en-US")
.phoneNumber("+1 (604) 555-1234;ext=5678")
.phoneNumberVerified(false)
.claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
.updatedAt("1970-01-01T00:00:00Z")
.build()
.getClaims();
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.jwt;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@Configuration
public class JwtTokenCustomizerConfig {
// @formatter:off
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
// @formatter:on
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.jwt;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import java.util.function.Function;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class JwtUserInfoMapperSecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> { // <2>
OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
return new OidcUserInfo(principal.getToken().getClaims());
};
// @formatter:off
authorizationServerConfigurer
.oidc((oidc) -> oidc
.userInfoEndpoint((userInfo) -> userInfo
.userInfoMapper(userInfoMapper) // <3>
)
);
http
.securityMatcher(endpointsMatcher)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(resourceServer -> resourceServer
.jwt(Customizer.withDefaults()) // <4>
)
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
.apply(authorizationServerConfigurer); // <5>
// @formatter:on
return http.build();
}
// @fold:on
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// @fold:off
}

View File

@@ -0,0 +1,6 @@
server:
port: 9000
logging:
level:
org.springframework.security: trace