spring-security-oauth2-authorization-server + angular-auth-oidc-client

spring-security-oauth2-authorization-server + angular-auth-oidc-client

我正在使用

"angular-auth-oidc-client": "^13.1.0",
 <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
            <version>0.2.2</version>
 </dependency>

我在我的应用程序中使用“OpenId Connect”。如果我使用 Keycloak,我的应用程序运行良好。我喜欢将其替换为 spring-security-oauth2-authorization-server。我遇到了一些我无法解决的问题。

我点击我的应用程序,然后我被重定向到登录 UI。我输入 User + PW 并表示同意。然后我再次发回我的应用程序。在这里我看到以下问题

“this.storagePersistenceService”中没有匹配的数据。

你有什么想法可能是错误的吗

此致 G




import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
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.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;
import java.util.UUID;

/**
 * @author Joe Grandja
 * @since 0.0.1
 */
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfigNew {

    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("*"));
        configuration.setAllowedMethods(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(Customizer.withDefaults()).cors().configurationSource(corsConfigurationSource()).and().build();
    }

    // @formatter:off
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("medical-share-openid-connect-client-id")
                .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:4200/")
                .redirectUri("http://127.0.0.1:4200")
                .redirectUri("http://localhost:4200/")
//                .redirectUri("http://127.0.0.1:4200/login/oauth2/code/messaging-client-oidc")
//                .redirectUri("http://127.0.0.1:4200/authorized")
                .scope(OidcScopes.OPENID)
                .scope("offline_access")
//                .scope("message.read")
//                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        // Save registered client in db as if in-memory
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(registeredClient);

        return registeredClientRepository;
    }
    // @formatter:on

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = Jwks.generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    @Bean
    public ProviderSettings providerSettings() {
//        return ProviderSettings.builder().issuer("http://auth-server:9000").build();
        return ProviderSettings.builder().issuer("http://localhost:8088").build();
    }

    @Bean
    public EmbeddedDatabase embeddedDatabase() {
        // @formatter:off
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
                .build();
        // @formatter:on
    }

}


import org.springframework.context.annotation.Bean;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;
@EnableWebSecurity
public class DefaultSecurityConfigNew {

    // @formatter:off
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .formLogin(withDefaults());
        return http.build();
    }
    // @formatter:on

    // @formatter:off
    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("U-294b70ca67df4c018f59950f35314944")
                .password("U-294b70ca67df4c018f59950f35314944")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
    // @formatter:on

}
/*
 * Copyright 2020-2021 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.
 */


import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;

import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

/**
 * @author Joe Grandja
 * @since 0.1.0
 */
public final class Jwks {

    private Jwks() {
    }

    public static RSAKey generateRsa() {
        KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // @formatter:off
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        // @formatter:on
    }

    public static ECKey generateEc() {
        KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
        Curve curve = Curve.forECParameterSpec(publicKey.getParams());
        // @formatter:off
        return new ECKey.Builder(curve, publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        // @formatter:on
    }

    public static OctetSequenceKey generateSecret() {
        SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
        // @formatter:off
        return new OctetSequenceKey.Builder(secretKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        // @formatter:on
    }
}

/*
 * Copyright 2020-2021 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.
 */


import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;

/**
 * @author Joe Grandja
 * @since 0.1.0
 */
final class KeyGeneratorUtils {

    private KeyGeneratorUtils() {
    }

    static SecretKey generateSecretKey() {
        SecretKey hmacKey;
        try {
            hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return hmacKey;
    }

    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;
    }

    static KeyPair generateEcKey() {
        EllipticCurve ellipticCurve = new EllipticCurve(
                new ECFieldFp(
                        new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
                new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
                new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
        ECPoint ecPoint = new ECPoint(
                new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
                new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
        ECParameterSpec ecParameterSpec = new ECParameterSpec(
                ellipticCurve,
                ecPoint,
                new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
                1);

        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
            keyPairGenerator.initialize(ecParameterSpec);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }
}

import {NgModule} from '@angular/core';
import {AuthModule, LogLevel} from 'angular-auth-oidc-client';

@NgModule({
  imports: [
    AuthModule.forRoot({
      config: {
        configId: 'ms-angular-auth-oidc-client-lib-id',
        logLevel: LogLevel.Debug,
        historyCleanupOff: false,
        authority: 'http://localhost:8088',
        redirectUrl: 'http://127.0.0.1:4200' + '/',
        postLogoutRedirectUri: 'http://127.0.0.1:4200' + '/',
        clientId: 'medical-share-openid-connect-client-id',
        scope: 'openid offline_access',
        responseType: 'code',
        silentRenew: true,
        renewTimeBeforeTokenExpiresInSeconds: 30,
        ignoreNonceAfterRefresh: true,
        useRefreshToken: true,
        autoUserInfo: false, // change that as it is in the example for spring security like this
        secureRoutes: [
          'http://localhost:4200/',
          'localhost:4200/',
          '127.0.0.1:4200/',
          'http://127.0.0.1:4200/',
          'http://localhost:4200/',
          'http://localhost:8081',
          'localhost:8081',
        ],
      },
    }),
  ],
  exports: [AuthModule],
})
export class AuthConfigModule {}

09:38:13.475 Navigated to http://localhost:4200/should-login
09:38:13.682 [webpack-dev-server] Live Reloading enabled. index.js:548
09:38:13.875 [DEBUG] ms-angular-auth-oidc-client-lib-id - Did not find any configured route for route http://localhost:8088/.well-known/openid-configuration angular-auth-oidc-client.mjs:160:20
09:38:13.878 [DEBUG] ms-angular-auth-oidc-client-lib-id - Did not find any configured route for route ../../assets/config/config.json angular-auth-oidc-client.mjs:160:20
09:38:13.956 Angular is running in development mode. Call enableProdMode() to enable production mode. core.mjs:24856:16
09:38:13.967 [DEBUG] ms-angular-auth-oidc-client-lib-id - Working with config 'ms-angular-auth-oidc-client-lib-id' using http://localhost:8088 angular-auth-oidc-client.mjs:160:20
09:38:13.968 [DEBUG] ms-angular-auth-oidc-client-lib-id - currentUrl to check auth with:  http://localhost:4200/should-login angular-auth-oidc-client.mjs:157:20
09:38:13.969 [DEBUG] ms-angular-auth-oidc-client-lib-id - checkAuth completed - firing events now. isAuthenticated: false angular-auth-oidc-client.mjs:160:20
09:38:13.970 Navigated to http://localhost:8088/oauth2/authorize?client_id=medical-share-openid-connect-client-id&redirect_uri=http%3A%2F%2F127.0.0.1%3A4200%2F&response_type=code&scope=openid%20offline_access&nonce=e9bd6b927b16552d4560ef708d5fd8ca49mFmzaxQ&state=771502df2eae52657faf84e1e9cf3253b3TN7VCvc&code_challenge=0AggJb7Z2ONJmNU7rAqXh9BmKf5YeEcrLlmw9lgoMJE&code_challenge_method=S256
09:38:13.970 [DEBUG] ms-angular-auth-oidc-client-lib-id - BEGIN Authorize OIDC Flow, no auth data angular-auth-oidc-client.mjs:160:20
09:38:13.971 [DEBUG] ms-angular-auth-oidc-client-lib-id - Nonce created. nonce:e9bd6b927b16552d4560ef708d5fd8ca49mFmzaxQ angular-auth-oidc-client.mjs:160:20
09:38:13.971 [DEBUG] ms-angular-auth-oidc-client-lib-id - Authorize created. adding myautostate: 771502df2eae52657faf84e1e9cf3253b3TN7VCvc angular-auth-oidc-client.mjs:160:20
09:38:13.994 downloadable font: download failed (font-family: "sdx-icons" style:normal weight:400 stretch:100 src index:1): status=2152398850 source: http://localhost:4200/sdx-icons-p.woff2
09:38:14.019 [webpack-dev-server] Disconnected! index.js:548
09:38:14.021 [webpack-dev-server] Trying to reconnect... index.js:548
09:38:16.459 Navigated to http://localhost:8088/login
09:38:16.801 [webpack-dev-server] Live Reloading enabled. index.js:548
09:38:16.983 [DEBUG] ms-angular-auth-oidc-client-lib-id - Did not find any configured route for route http://localhost:8088/.well-known/openid-configuration angular-auth-oidc-client.mjs:160:20
09:38:16.988 [DEBUG] ms-angular-auth-oidc-client-lib-id - Did not find any configured route for route ../../assets/config/config.json angular-auth-oidc-client.mjs:160:20
09:38:17.056 Angular is running in development mode. Call enableProdMode() to enable production mode. core.mjs:24856:16
09:38:17.069 ERROR Error: Uncaught (in promise): () => new Error(`could not find matching config for state ${stateParamFromUrl}`)
    Angular 19
    RxJS 21
    ZoneAwarePromise Angular
    toPromise RxJS
    getEnvironmentConfiguration config.service.ts:25
    initAppConfig init.app.config.ts:6
    Angular 24
    7004 main.ts:14
    Webpack 7
core.mjs:6484:12






21.3.22 我能够使 spring-security-oauth2-authorization-server 使用下面的配置。

@SteveRiesenberg 您认为该代码有任何改进/简化的可能吗?



import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
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.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.UUID;

/**
 * @author Joe Grandja
 * @since 0.0.1
 */
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfigNew {

    public static CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
//        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(Customizer.withDefaults()).cors().configurationSource(corsConfigurationSource()).and().build();
    }

    // @formatter:off
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("medical-share-openid-connect-client-id")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://127.0.0.1:4200")
                .redirectUri("http://127.0.0.1:4200/")
                .redirectUri("http://127.0.0.1:4200/silent-renew.html")
                .scope(OidcScopes.OPENID)
                .scope("offline_access")
//                .scope("message.read")
//                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).requireProofKey(true).build())
                .build();

        // Save registered client in db as if in-memory
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(publicClient);

        return registeredClientRepository;
    }
    // @formatter:on

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = Jwks.generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder().issuer("http://127.0.0.1:8088").build();
//        return ProviderSettings.builder().issuer("http://localhost:8088").build();
    }

    @Bean
    public EmbeddedDatabase embeddedDatabase() {
        // @formatter:off
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
                .build();
        // @formatter:on
    }

}


import org.springframework.context.annotation.Bean;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static com.test.kubernetes.authorizationserver.application.sample.config.AuthorizationServerConfigNew.corsConfigurationSource;
import static org.springframework.security.config.Customizer.withDefaults;

@EnableWebSecurity
public class DefaultSecurityConfigNew {

    // @formatter:off
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.cors().configurationSource(corsConfigurationSource()).and()
                .authorizeRequests()
//                .antMatchers("/.well-known/**", "/oauth2/**", "/userinfo")
                .antMatchers("/**")
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/login")
                .authenticated()
                .and()
                .formLogin(withDefaults());

        return http.cors().configurationSource(corsConfigurationSource()).and().build();
    }
    // @formatter:on

    // @formatter:off
    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("U-294b70ca67df4c018f59950f35314944")
                .password("U-294b70ca67df4c018f59950f35314944")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
    // @formatter:on

}
import {NgModule} from '@angular/core';
import {AuthModule, LogLevel} from 'angular-auth-oidc-client';

@NgModule({
  imports: [
    AuthModule.forRoot({
      config: {
        configId: 'ms-angular-auth-oidc-client-lib-id',
        logLevel: LogLevel.Debug,
        historyCleanupOff: false,
        // authority: 'http://localhost:8088/realms/master',
        authority: 'http://127.0.0.1:8088',
        redirectUrl: 'http://127.0.0.1:4201/',
        // redirectUrl: 'http://localhost:4201/',
        postLogoutRedirectUri: 'http://127.0.0.1:4201/',
        clientId: 'medical-share-openid-connect-client-id',
        scope: 'openid offline_access',
        responseType: 'code',
        silentRenew: true,
        renewTimeBeforeTokenExpiresInSeconds: 10,
        autoUserInfo: false,
        ignoreNonceAfterRefresh: true,
        useRefreshToken: true,
        // autoCleanStateAfterAuthentication: false,
        secureRoutes: [
          'http://localhost:4201/',
          'localhost:4201/',
          '127.0.0.1:4201/',
          'http://127.0.0.1:4201/',
          'http://127.0.0.1:4201',
          'http://localhost:4201/',
          'http://localhost:8083',
          'localhost:8083',
          'http://127.0.0.1:8088',
          'http://127.0.0.1:8088/',
        ],
      },
    }),
  ],
  exports: [AuthModule],
})
export class AuthConfigModule {
}

我会尽力为您面临的 questions/challenges 序列提供答案。

根据问题的评论和更新,您似乎有一些不正确的配置需要解决。来自我们的第一轮评论:

  • 您似乎已经配置了机密客户端(带有客户端密码、客户端身份验证方法,requireProofKey(true) 未设置

注意:另外,请确保您是从 http://127.0.0.1:4200 而不是 http://localhost:4200.

浏览您的应用程序

根据您在问题中提供的更新:

  • 您不想在 cors 配置中注释掉 config.setAllowCredentials(true);,因为浏览器需要能够通过静默更新过程发送 JSESSIONID cookie。
  • Spring 授权服务器目前不支持 public 客户端的刷新令牌,因此我的示例不包含该选项 useRefreshToken: true.
  • 您的 .authorizeRequests() DSL 用法不正确。您不希望多次调用该方法,因为第二次调用会覆盖第一次调用。您还在默认过滤器链中定位了错误的端点。它应该看起来像示例:
// @formatter:off
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests.anyRequest().authenticated()
        )
        // Note: You can also make the corsConfigurationSource() non-static
        // and annotated with @Bean. Then use instead:
        // .cors(Customizer.withDefaults())
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .formLogin(Customizer.withDefaults());
    return http.build();
}
// @formatter:on

可能还有其他问题,不好说。我建议从 this working sample (see also the angular-client auth config) 开始,并根据您正在考虑的任何自定义项对其进行改进,看看是否引入了问题。

我终于成功了:-)非常感谢@Steve。

请注意,我知道没有 RefreshToken 支持。因此,我将令牌的生命周期设为 1 天。


import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.RSAKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.lang.invoke.MethodHandles;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

@RestController
@RequestMapping()
public class OpenIdConnectMockController {

    static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private KeyPair rsaKeyPair;
    private RSAKey jwkRsaPublicKey;

    @PostConstruct
    public void generateKey() {
        this.rsaKeyPair = generateRsaKey();
        this.jwkRsaPublicKey = generateRsa(this.rsaKeyPair);
    }

    /**
     * Note this method is currently not use, but here for future use.
     * We use the same URL as in the Keycloak, so we have not to change things when running the thing against ms-backend-test-openid-connect-mock or Keycloak.
     */
    @GetMapping(path = "/realms/master/protocol/openid-connect/certs", produces = "application/json")
    public String keys() {
        logger.info("Keys was called {}", this.jwkRsaPublicKey.toString());
        return "{\"keys\":[" + this.jwkRsaPublicKey.toString() + "]}";
    }

    /**
     * This is used in the SystemTest. There we download the private key and sign the JWT.
     */
    @GetMapping(path = "/realms/master/protocol/openid-connect/private-key", produces = "application/json")
    public byte[] getPrivateKey() throws JOSEException {
        RSAKey privateKey = new RSAKey.Builder((RSAPublicKey) this.rsaKeyPair.getPublic()).privateKey(this.rsaKeyPair.getPrivate()).build();
        return privateKey.toRSAPrivateKey().getEncoded();
    }

    public RSAKey getJwkRsaPublicKey() {
        return this.jwkRsaPublicKey;
    }

    private RSAKey generateRsa(KeyPair keyPair) {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    private 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;
    }

}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.provisioning.InMemoryUserDetailsManager;

@Configuration
public class PersistedUserConfig {

    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("U-294b70ca67df4c018f59950f35314944")
                .password("U-294b70ca67df4c018f59950f35314944")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

}


import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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.*;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.PostConstruct;
import java.lang.invoke.MethodHandles;
import java.time.Duration;
import java.util.Objects;
import java.util.UUID;

import static org.springframework.security.config.Customizer.withDefaults;

@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class TestAuthorizationServerConfig {

    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());


    @PostConstruct
    public void publicNote() {
        logger.info("****************************************************************************************************************************");
        logger.info("****************************************************************************************************************************");
        logger.info("Note that the current spring authorisation server does not support token renewal. If you want to test that, use the Keycloak");
        logger.info("****************************************************************************************************************************");
        logger.info("****************************************************************************************************************************");
    }

    public static CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");

        config.addAllowedOrigin("127.0.0.1:4201");
        config.addAllowedOrigin("http://127.0.0.1:4201");
        config.addAllowedOrigin("localhost:4201");

        config.addAllowedOrigin("127.0.0.1:4200");
        config.addAllowedOrigin("http://127.0.0.1:4200");
        config.addAllowedOrigin("localhost:4200");

        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        return http.cors().configurationSource(corsConfigurationSource()).and()
                .authorizeRequests()
                .antMatchers("/**")
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/login")
                .authenticated()
                .and()
                .formLogin(withDefaults())
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(withDefaults()).cors().configurationSource(corsConfigurationSource()).and().build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("medical-share-openid-connect-client-id")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)

                .redirectUri("http://127.0.0.1:4200")
                .redirectUri("http://127.0.0.1:4200/")

                .redirectUri("http://127.0.0.1:4201")
                .redirectUri("http://127.0.0.1:4201/")
                .scope(OidcScopes.OPENID)
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
                .scope("offline_access")
//                .scope("message.read")
//                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).requireProofKey(true).build())
                .build();

        // Save registered client in db as if in-memory
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(publicClient);

        return registeredClientRepository;
    }

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource(OpenIdConnectMockController openIdConnectMockController) {
        RSAKey rsaKey = openIdConnectMockController.getJwkRsaPublicKey();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder().issuer("http://127.0.0.1:8088").build();
    }

    @Bean
    public EmbeddedDatabase embeddedDatabase() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
                .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
                .build();
    }

    /**
     * This is a bit a hack, but as we do not know how we integrate the HealthPlattform this is a very easy way to solve the Problem for the moment.
     */
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
        return context -> {
            Authentication principal = context.getPrincipal();
            if (Objects.equals(context.getTokenType().getValue(), "access_token") && principal instanceof UsernamePasswordAuthenticationToken) {
                User user = (User) principal.getPrincipal();
                context.getClaims()
                        .claim("name", user.getUsername());
            }
        };
    }

}

import {NgModule} from '@angular/core';
import {AuthModule, LogLevel} from 'angular-auth-oidc-client';

@NgModule({
  imports: [
    AuthModule.forRoot({
      config: {
        configId: 'ms-angular-auth-oidc-client-lib-id',
        logLevel: LogLevel.Debug,
        historyCleanupOff: false,
        // Keycloak
        // authority: 'http://localhost:8088/realms/master',
        authority: 'http://127.0.0.1:8088',
        redirectUrl: 'http://127.0.0.1:4201/',
        // Keycloak
        // redirectUrl: 'http://localhost:4201/',
        postLogoutRedirectUri: 'http://127.0.0.1:4201/',
        clientId: 'medical-share-openid-connect-client-id',
        scope: 'openid',
        responseType: 'code',
        silentRenew: true,
        renewTimeBeforeTokenExpiresInSeconds: 10,
        autoUserInfo: false,
        useRefreshToken: true,
        secureRoutes: [
          'http://127.0.0.1:4201',
          'http://localhost:4201',
        ],
      },
    }),
  ],
  exports: [AuthModule],
})
export class AuthConfigModule {
}