Migrate docs to Antora
Issue gh-1295
This commit is contained in:
committed by
Steve Riesenberg
parent
ec3d4fa7e1
commit
0f0424ad2a
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
116
docs/src/main/java/sample/extgrant/SecurityConfig.java
Normal file
116
docs/src/main/java/sample/extgrant/SecurityConfig.java
Normal 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
|
||||
|
||||
}
|
||||
173
docs/src/main/java/sample/gettingstarted/SecurityConfig.java
Normal file
173
docs/src/main/java/sample/gettingstarted/SecurityConfig.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
29
docs/src/main/java/sample/gettingstarted/application.yml
Normal file
29
docs/src/main/java/sample/gettingstarted/application.yml
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
155
docs/src/main/java/sample/jpa/entity/client/Client.java
Normal file
155
docs/src/main/java/sample/jpa/entity/client/Client.java
Normal 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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
56
docs/src/main/java/sample/pkce/ClientConfig.java
Normal file
56
docs/src/main/java/sample/pkce/ClientConfig.java
Normal 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[]
|
||||
|
||||
}
|
||||
95
docs/src/main/java/sample/pkce/SecurityConfig.java
Normal file
95
docs/src/main/java/sample/pkce/SecurityConfig.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
19
docs/src/main/java/sample/pkce/application.yml
Normal file
19
docs/src/main/java/sample/pkce/application.yml
Normal 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
|
||||
76
docs/src/main/java/sample/sociallogin/SecurityConfig.java
Normal file
76
docs/src/main/java/sample/sociallogin/SecurityConfig.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
23
docs/src/main/java/sample/sociallogin/application.yml
Normal file
23
docs/src/main/java/sample/sociallogin/application.yml
Normal 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
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
6
docs/src/main/resources/application.yml
Normal file
6
docs/src/main/resources/application.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
server:
|
||||
port: 9000
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: trace
|
||||
Reference in New Issue
Block a user