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 ?
Problem in this picture i can not see "Github" or "Google" to login with them enter image description here
Code
Dependencies
<?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);
}
}
In order to show links on the generated login page, the docs state:
Ideally, you should be providing a custom login page.