How to Store Github and Google Clientid's and ClientSecrets in DB in Spring Authorization Server?

119 Views Asked by At

I'm new to the spring authorization server and I want to log in with Github so I tried first to put the client-id and client-secret in the properties file but in the real world it mustn't stored in the properties file so I tried to store it in DB through ClientRegistrationRepository interface but it did not appear to me to log in with "Github" or "Google" words links to redirect me to authorization server of GitHub or Google

Has anyone tried this before Tell me about a tutorial or steps for it with Spring Authorization Server ?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>authorization-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>authorization-server</name>
    <description>authorization-server</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

AuthorizationServerConfig.java

package com.example.authorizationserver.config.security;

import com.example.authorizationserver.providers.SuccessfulAuthenticationHandler;
import com.example.authorizationserver.services.ClientRegistrationServiceImpl;
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 lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

@EnableWebSecurity
@Configuration
@Log4j2
@AllArgsConstructor
public class AuthorizationServerConfig {


    private final SuccessfulAuthenticationHandler successfulLoginWithGoogleOrGithub;
    private final ClientRegistrationServiceImpl clientRegistrationService;

    @Bean
    @Order(1)
    public SecurityFilterChain asFilterChain(HttpSecurity http) throws Exception {

        // we must disable csrf for this endpoint and make it access from any one at the first filter
        http.csrf(csrf -> csrf.ignoringRequestMatchers("/auth/register/**"));

        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);


        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults())
                .oidc(oidc -> oidc.clientRegistrationEndpoint(Customizer.withDefaults()));

        http
                //  Resource server support that allows User Info requests to be authenticated with access tokens
                // this allows me to user /userinfo endpoint in postman with access_token in the header
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
                .exceptionHandling(
                c -> c.defaultAuthenticationEntryPointFor(
                        new LoginUrlAuthenticationEntryPoint("/login"),
                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                )
        );



        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain appFilterChain(HttpSecurity http) throws Exception {


        // we must disable csrf for this endpoint and make it access from any one at the second filter also , because it moved from first filter to
        // the second filter
        http.csrf(csrf -> csrf.ignoringRequestMatchers("/auth/register/**"));


        http.formLogin(Customizer.withDefaults()); // to view to user normal username and password form


        // to display the word : Github to allow user to click on it to log in with username and password of Github
        // and success handler to save user to db after successfully login
        http.oauth2Login(oauth2Login -> oauth2Login
                .clientRegistrationRepository(clientRegistrationService)
                .successHandler(successfulLoginWithGoogleOrGithub));

        return http.build();
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        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;
    }
}

  • OAuth2ClientRegistrationEntity this entity store credentials about Google or GitHub or any provider and i store the value according class called org.springframework.security.oauth2.client.registration.ClientRegistration
package com.example.authorizationserver.entities;


import jakarta.persistence.*;
import lombok.*;

// this entity stored credentials about Google or Github db
@Entity
@Table(name = "oauth2_clients")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class OAuth2ClientRegistrationEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String registrationId;

    private String clientId;

    private String clientSecret;

    private String clientName;

    private String authenticationMethod;

    private String authorizationGrantType;

    private String redirectUri;

    private String scopes;

    // this store provider details for simplicity
    private String authorizationUri;

    private String tokenUri;

    private String jwkSetUri;

    private String issuerUri;

    private String userInfoUri;

}

  • OAuth2ClientRegistrationRepo this to retrieve google or github credentials from db
package com.example.authorizationserver.repositories;

import com.example.authorizationserver.entities.OAuth2ClientRegistrationEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OAuth2ClientRegistrationRepo extends JpaRepository<OAuth2ClientRegistrationEntity,Long> {

    OAuth2ClientRegistrationEntity findByRegistrationId(String registrationId);
}

  • ClientRegistrationServiceImpl
package com.example.authorizationserver.services;


import com.example.authorizationserver.entities.OAuth2ClientRegistrationEntity;
import com.example.authorizationserver.repositories.OAuth2ClientRegistrationRepo;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.stereotype.Service;

@Service
@Log4j2
public class ClientRegistrationServiceImpl implements ClientRegistrationRepository {

    @Autowired
    private OAuth2ClientRegistrationRepo oAuth2ClientRegistrationRepo;

    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
        OAuth2ClientRegistrationEntity entity = oAuth2ClientRegistrationRepo.findByRegistrationId(registrationId);

        if(entity == null) {
            log.error(">>>>> NO REGISTRATION_ID FOUND WITH THIS ID = " + registrationId);
            return null;
        }
        return this.toModel(entity);
    }


    private ClientRegistration toModel(OAuth2ClientRegistrationEntity entity) {
        return ClientRegistration.withRegistrationId(entity.getRegistrationId())
                .clientName(entity.getClientName())
                .clientId(entity.getClientId())
                .clientSecret(entity.getClientSecret())
                .clientAuthenticationMethod(new ClientAuthenticationMethod(entity.getAuthenticationMethod()))
                .authorizationGrantType(new AuthorizationGrantType(entity.getAuthorizationGrantType()))
                .redirectUri(entity.getRedirectUri())
                .scope(
                        entity.getScopes().split(",")
                )
                .authorizationUri(entity.getAuthorizationUri())
                .tokenUri(entity.getTokenUri())
                .jwkSetUri(entity.getJwkSetUri())
                .issuerUri(entity.getIssuerUri())
                .userInfoUri(entity.getUserInfoUri())
                .build();
    }


}

  • OAuth2ClientRegistrationTableInitializer this class to add data of google or github to db
package com.example.authorizationserver.config.db;


import com.example.authorizationserver.entities.OAuth2ClientRegistrationEntity;
import com.example.authorizationserver.repositories.OAuth2ClientRegistrationRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;

@Configuration
public class OAuth2ClientRegistrationTableInitializer {

    @Autowired
    private OAuth2ClientRegistrationRepo oAuth2ClientRegistrationRepo;


    public void addAllOAuth2ClientsToDB() {
        OAuth2ClientRegistrationEntity google = OAuth2ClientRegistrationEntity.builder()
                .registrationId("google")
                .clientName("Google")
                .clientId("my-google-client-id")
                .clientSecret("my-google-client-secret")
                .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
                .scopes("openid,profile,email,address,phone")
                .authenticationMethod("client_secret_basic")
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
                .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
                .tokenUri("https://www.googleapis.com/oauth2/v4/token")
                .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
                .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
                .build();

        oAuth2ClientRegistrationRepo.save(google);
    }
}

1

There are 1 best solutions below

0
On

In order to show links on the generated login page, the docs state:

For DefaultLoginPageGeneratingFilter to show links for configured OAuth Clients, the registered ClientRegistrationRepository needs to also implement Iterable<ClientRegistration>. See InMemoryClientRegistrationRepository for reference.

Ideally, you should be providing a custom login page.