Skip to content

Commit

Permalink
Merge pull request Baeldung#203 from sampada07/JAVA-409
Browse files Browse the repository at this point in the history
JAVA-409: Update SSO with OAuth2 article
  • Loading branch information
lor6 authored Jun 29, 2020
2 parents a2c5665 + 4376638 commit c86845d
Show file tree
Hide file tree
Showing 51 changed files with 3,564 additions and 0 deletions.
17 changes: 17 additions & 0 deletions oauth-sso/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Spring Security OAuth - Dynamic Client Registration

### Relevant information:

1. `sso-authorization-server` is a Keycloak Authorization Server wrapped as a Spring Boot application
2. There is one OAuth Client registered in the Authorization Server:
1. Client Id: ssoClient
2. Client secret: ssoClientSecret
3. Redirect Uri: http://localhost:8089/
3. `sso-resource-server` is a Spring Boot based RESTFul API, acting as a backend Application
4. `sso-client-app-1` and `sso-client-app-2` are two identical Spring MVC Thymeleaf App acting our front end. They are available at [http://localhost:8082/ui-one/](http://localhost:8082/ui-one) and [http://localhost:8084/ui-two/](http://localhost:8084/ui-two/) respectively.
5. There are two users registered in the Authorization Server:
1. [email protected] / 123
2. [email protected] / pass

## Relevant Articles:

18 changes: 18 additions & 0 deletions oauth-sso/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<description>New OAuth2 Stack in Spring Security 5</description>

<groupId>com.baeldung</groupId>
<artifactId>oauth-sso</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>sso-authorization-server</module>
<module>sso-resource-server</module>
<module>sso-client-app-1</module>
<module>sso-client-app-2</module>
</modules>

</project>
114 changes: 114 additions & 0 deletions oauth-sso/sso-authorization-server/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.baeldung</groupId>
<artifactId>sso-authorization-server</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent>

<dependencies>

<!-- web -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- persistence -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Keycloak server -->

<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-dependencies-server-all</artifactId>
<version>${keycloak.version}</version>
<type>pom</type>
</dependency>

<!-- config properties processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- test -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/*LiveTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

<developers>
<developer>
<email>[email protected]</email>
<name>Eugen Paraschiv</name>
<url>https://github.com/eugenp</url>
<id>eugenp</id>
</developer>
</developers>

<properties>
<!-- non-dependencies -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>13</java.version>

<keycloak.version>10.0.1</keycloak.version>
<resteasy.version>3.11.2.Final</resteasy.version>
</properties>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.baeldung.auth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;

import com.baeldung.auth.config.KeycloakServerProperties;

@SpringBootApplication(exclude = LiquibaseAutoConfiguration.class)
@EnableConfigurationProperties({ KeycloakServerProperties.class })
public class AuthorizationServerApp {

private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerApp.class);

public static void main(String[] args) throws Exception {
SpringApplication.run(AuthorizationServerApp.class, args);
}

@Bean
ApplicationListener<ApplicationReadyEvent> onApplicationReadyEventListener(ServerProperties serverProperties,
KeycloakServerProperties keycloakServerProperties) {

return (evt) -> {

Integer port = serverProperties.getPort();
String keycloakContextPath = keycloakServerProperties.getContextPath();

LOG.info("Embedded Keycloak started: http://localhost:{}{} to use keycloak", port, keycloakContextPath);
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.baeldung.auth.config;

import java.util.NoSuchElementException;

import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.util.JsonConfigProviderFactory;
import org.keycloak.util.JsonSerialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.baeldung.auth.config.KeycloakServerProperties.AdminUser;

public class EmbeddedKeycloakApplication extends KeycloakApplication {

private static final Logger LOG = LoggerFactory.getLogger(EmbeddedKeycloakApplication.class);

static KeycloakServerProperties keycloakServerProperties;

protected void loadConfig() {
JsonConfigProviderFactory factory = new RegularJsonConfigProviderFactory();
Config.init(factory.create()
.orElseThrow(() -> new NoSuchElementException("No value present")));
}

public EmbeddedKeycloakApplication() {

super();

createMasterRealmAdminUser();

createBaeldungRealm();
}

private void createMasterRealmAdminUser() {

KeycloakSession session = getSessionFactory().create();

ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);

AdminUser admin = keycloakServerProperties.getAdminUser();

try {
session.getTransactionManager().begin();
applianceBootstrap.createMasterRealmUser(admin.getUsername(), admin.getPassword());
session.getTransactionManager().commit();
} catch (Exception ex) {
LOG.warn("Couldn't create keycloak master admin user: {}", ex.getMessage());
session.getTransactionManager().rollback();
}

session.close();
}

private void createBaeldungRealm() {
KeycloakSession session = getSessionFactory().create();

try {
session.getTransactionManager().begin();

RealmManager manager = new RealmManager(session);
Resource lessonRealmImportFile = new ClassPathResource(keycloakServerProperties.getRealmImportFile());

manager.importRealm(
JsonSerialization.readValue(lessonRealmImportFile.getInputStream(), RealmRepresentation.class));

session.getTransactionManager().commit();
} catch (Exception ex) {
LOG.warn("Failed to import Realm json file: {}", ex.getMessage());
session.getTransactionManager().rollback();
}

session.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.baeldung.auth.config;

import javax.naming.CompositeName;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameParser;
import javax.naming.NamingException;
import javax.naming.spi.NamingManager;
import javax.sql.DataSource;

import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EmbeddedKeycloakConfig {

@Bean
ServletRegistrationBean<HttpServlet30Dispatcher> keycloakJaxRsApplication(
KeycloakServerProperties keycloakServerProperties, DataSource dataSource) throws Exception {

mockJndiEnvironment(dataSource);
EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties;

ServletRegistrationBean<HttpServlet30Dispatcher> servlet = new ServletRegistrationBean<>(
new HttpServlet30Dispatcher());
servlet.addInitParameter("javax.ws.rs.Application", EmbeddedKeycloakApplication.class.getName());
servlet.addInitParameter(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX,
keycloakServerProperties.getContextPath());
servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, "true");
servlet.addUrlMappings(keycloakServerProperties.getContextPath() + "/*");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);

return servlet;
}

@Bean
FilterRegistrationBean<KeycloakSessionServletFilter> keycloakSessionManagement(
KeycloakServerProperties keycloakServerProperties) {

FilterRegistrationBean<KeycloakSessionServletFilter> filter = new FilterRegistrationBean<>();
filter.setName("Keycloak Session Management");
filter.setFilter(new KeycloakSessionServletFilter());
filter.addUrlPatterns(keycloakServerProperties.getContextPath() + "/*");

return filter;
}

private void mockJndiEnvironment(DataSource dataSource) throws NamingException {
NamingManager.setInitialContextFactoryBuilder((env) -> (environment) -> new InitialContext() {

@Override
public Object lookup(Name name) {
return lookup(name.toString());
}

@Override
public Object lookup(String name) {

if ("spring/datasource".equals(name)) {
return dataSource;
}

return null;
}

@Override
public NameParser getNameParser(String name) {
return CompositeName::new;
}

@Override
public void close() {
// NOOP
}
});
}
}
Loading

0 comments on commit c86845d

Please sign in to comment.