簡體   English   中英

如何通過 Spring 引導動態更改 Spring Security 中的 client_secret

[英]How to dynamically change client_secret in Spring Security with Spring boot

現在我有這個配置:

spring:
  security:
    oauth2:
      client:
        registration:
          sbbol:
            client-id: zdcffffff
            client-secret: ffffffffff
            scope:
              - openid
            client-authentication-method: post
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            client-authentication-scheme: form
        provider:
          sbbol:
            authorization-uri: ${SBBOL_AUTH_URI}
            token-uri: ${SBBOL_AUTH_URI}
            user-info-uri: ${SBBOL_AUTH_URI}
            user-name-attribute: sub
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable();
        http.cors().disable();
        http.csrf().disable();
        http.requestMatchers()
            .antMatchers("/login", "/oauth2/authorization/sbbol", "/login/oauth2/code/sbbol")
            .and()
            .authorizeRequests().anyRequest().authenticated();
        http.oauth2Login()
            .defaultSuccessUrl("/user")
            .permitAll();
    }
}

這可行,但我的提供商要求我通過 rest api 調用每 30 天更改一次客戶端密碼。 我有一個問題,如何在 Spring Security 中設置新的客戶端密碼? 也許我可以將配置存儲在數據庫中?

我為 org.springframework.security.oauth2.client.registration.ClientRegistrationRepository 創建了自己的實現。 我可以將設置存儲在數據庫中並進行更改。

@Component
@RequiredArgsConstructor
public class JdbcClientRegistrationRepository implements ClientRegistrationRepository {

    private final SsoProviderConfigurationRepository ssoProviderConfigurationRepository;

    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
        Assert.hasText(registrationId, "registrationId cannot be empty");
        SsoProviderConfiguration providerConfiguration = ssoProviderConfigurationRepository.findByRegistrationId(registrationId)
            .orElseThrow(() -> new RuntimeException("ClientRegistration not found by id=" + registrationId));

        String[] scopes = providerConfiguration.getScope().split(",");
        return ClientRegistration.withRegistrationId(providerConfiguration.getRegistrationId())
            .clientId(providerConfiguration.getClientId())
            .clientSecret(providerConfiguration.getClientSecret())
            .clientName(providerConfiguration.getClientName())
            .authorizationGrantType(new AuthorizationGrantType(providerConfiguration.getAuthorizationGrantType()))
            .authorizationUri(providerConfiguration.getAuthorizationUri())
            .clientAuthenticationMethod(new ClientAuthenticationMethod(providerConfiguration.getClientAuthenticationMethod()))
            .scope(scopes)
            .tokenUri(providerConfiguration.getTokenUri())
            .userInfoAuthenticationMethod(new AuthenticationMethod(providerConfiguration.getAuthenticationMethod()))
            .userInfoUri(providerConfiguration.getUserInfoUri())
            .userNameAttributeName(providerConfiguration.getUserNameAttributeName())
            .redirectUri(providerConfiguration.getRedirectUri())
            .build();
    }
}

我的實體

@Entity
@Table(name = "sso_provider_configuration")
@Getter
@Setter
@NoArgsConstructor
public class SsoProviderConfiguration implements Serializable {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    private String registrationId;

    private String clientId;

    private String clientSecret;

    private String clientAuthenticationMethod;

    private String authorizationGrantType;

    private String redirectUri;

    private String scope;

    private String clientName;

    private String authorizationUri;

    private String tokenUri;

    private String jwkSetUri;

    private String issuerUri;

    private String authenticationMethod;

    private String userNameAttributeName;

    private String UserInfoUri;
}

存儲庫

public interface SsoProviderConfigurationRepository extends JpaRepository<SsoProviderConfiguration, Long> {

    Optional<SsoProviderConfiguration> findByRegistrationId(String code);
}

我有這個可能有用,它不是動態的,但你可以解決

@Value("${security.oauth.client.id}")
private String clientId;

@Value("${security.oauth.client.password}")
private String clientPassword;


@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

    
    
    clients.inMemory()
    .withClient(clientId)
    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
    .scopes("read", "write", "trust")
    .secret(clientPassword)
    .resourceIds("resource_id")
    .accessTokenValiditySeconds(accessTokenTimeOut)
    .refreshTokenValiditySeconds(refreshTokenTimeOut)
   ;

    
}

我無法通過實現 ClientRegistrationRepository 創建 JdbcClientRegistrationRepository class。 它顯示它不可用,並且 ClientRegistration class 也不可用。 @Aleksandr Erokhin,如果您使用最新的 spring-security-oauth2-authorization-server:0.2.0 版本實現此場景,您能否分享代碼?

添加此配置 class:

import com.yourmom.security.oauth2.AppleClientSecretGenerator;
import com.yourmom.security.oauth2.ClientSecretGenerator;
import com.yourmom.service.DynamicInMemoryClientRegistrationRepository;
import java.util.ArrayList;
import java.util.HashMap;
import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;


@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({OAuth2ClientProperties.class})
@Conditional({ClientsConfiguredCondition.class})
class OAuthProps {
    OAuthProps() {
    }

    @Bean
    public DynamicInMemoryClientRegistrationRepository clientRegistrations(OAuth2ClientProperties properties,AppleClientSecretGenerator appleClientSecretGenerator) {
       return new DynamicInMemoryClientRegistrationRepository(
           new ArrayList(OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values()),
           new HashMap<String, ClientSecretGenerator>(){{
               put("apple",appleClientSecretGenerator);
           }}
       );
    }
}

這個界面:

public interface ClientSecretGenerator {
    public String generateClientSecret() throws Exception;
}

這個實現(如果你喜歡,它就是我使用它的目的)

import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.File;
import java.io.FileReader;
import java.security.PrivateKey;
import java.util.Date;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

@Service
public class AppleClientSecretGenerator implements ClientSecretGenerator {

    @Value("${spring.security.oauth2.client.registration.apple.keyId}")
    private String appleKeyId;

    @Value("${spring.security.oauth2.client.registration.apple.teamId}")
    private String appleTeamId;

    @Value("${spring.security.oauth2.client.registration.apple.clientId}")
    private String clientId;

    @Override
    public String generateClientSecret() throws Exception {
        // Generate a private key for token verification from your end with your creds
        PrivateKey pKey = generatePrivateKey();
        return Jwts.builder()
            .setHeaderParam(JwsHeader.KEY_ID, appleKeyId)
            .setIssuer(appleTeamId)
            .setAudience("https://appleid.apple.com")
            .setSubject(clientId)
            .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 500)))
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .signWith(SignatureAlgorithm.ES256,pKey)
            .compact();
    }

    private PrivateKey generatePrivateKey() throws Exception {
        // here i have added cert at resource/apple folder. So if you have added somewhere else, just replace it with your path ofcert
        File file = ResourceUtils.getFile("classpath:security/oauth/apple/AuthKey_"+appleKeyId+".p8");
        final PEMParser pemParser = new PEMParser(new FileReader(file));
        final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
        final PrivateKey pKey = converter.getPrivateKey(object);
        pemParser.close();
        return pKey;
    }
}

最后是這個ClientRegistrationRepository


import com.yourmom.security.oauth2.ClientSecretGenerator;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.util.Assert;

public class DynamicInMemoryClientRegistrationRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
    private final Map<String, ClientRegistration> registrations;
    private final Map<String, ClientSecretGenerator> clientSecretGeneratorMap;


    public DynamicInMemoryClientRegistrationRepository(List<ClientRegistration> clientRegistrations,Map<String, ClientSecretGenerator> clientSecretGeneratorMap) {
        this.clientSecretGeneratorMap = clientSecretGeneratorMap;
        registrations = createRegistrationsMap(clientRegistrations);
    }

    private static Map<String, ClientRegistration> createRegistrationsMap(List<ClientRegistration> registrations) {
        Assert.notEmpty(registrations, "registrations cannot be empty");
        return toUnmodifiableConcurrentMap(registrations);
    }

    private static Map<String, ClientRegistration> toUnmodifiableConcurrentMap(List<ClientRegistration> registrations) {
        ConcurrentHashMap<String, ClientRegistration> result = new ConcurrentHashMap();
        Iterator var2 = registrations.iterator();

        while(var2.hasNext()) {
            ClientRegistration registration = (ClientRegistration)var2.next();
            Assert.state(!result.containsKey(registration.getRegistrationId()), () -> {
                return String.format("Duplicate key %s", registration.getRegistrationId());
            });
            result.put(registration.getRegistrationId(), registration);
        }

        return Collections.unmodifiableMap(result);
    }


    public ClientRegistration findByRegistrationId(String registrationId) {
        Assert.hasText(registrationId, "registrationId cannot be empty");

        ClientRegistration immutableClientRegistration = this.registrations.get(registrationId);

        try {
            return ClientRegistration
                .withRegistrationId(registrationId)
                .clientId(immutableClientRegistration.getClientId())
                .authorizationGrantType(immutableClientRegistration.getAuthorizationGrantType())
                .clientName(immutableClientRegistration.getClientName())
                .clientAuthenticationMethod(immutableClientRegistration.getClientAuthenticationMethod())
                .clientSecret(clientSecretGeneratorMap.get(registrationId) != null ?  clientSecretGeneratorMap.get(registrationId).generateClientSecret() : immutableClientRegistration.getClientSecret())
                .redirectUri(immutableClientRegistration.getRedirectUri())
                .scope(immutableClientRegistration.getScopes())
                .providerConfigurationMetadata(immutableClientRegistration.getProviderDetails().getConfigurationMetadata())
                .issuerUri(immutableClientRegistration.getProviderDetails().getIssuerUri())
                .jwkSetUri(immutableClientRegistration.getProviderDetails().getJwkSetUri())
                .tokenUri(immutableClientRegistration.getProviderDetails().getTokenUri())
                .authorizationUri(immutableClientRegistration.getProviderDetails().getAuthorizationUri())
                                .userInfoUri(immutableClientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
                .userInfoAuthenticationMethod(immutableClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())
                .userNameAttributeName(immutableClientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName())
                .build();
        } catch (Exception e) {
            throw new RuntimeException("Failed while generating client secret: " + e,e);
        }

    }

    public Iterator<ClientRegistration> iterator() {
        return this.registrations.values().iterator();
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM