Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Login and Approval Interface #3

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ build/

### VS Code ###
.vscode/

### Javascript ###
.node_modules
.env
204 changes: 204 additions & 0 deletions authorization-server-backend/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<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>
<parent>
<groupId>dev.rexijie.oauth</groupId>
<artifactId>oauth2-server</artifactId>
<version>0.1.1</version>
</parent>

<artifactId>authorization-server-backend</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>authorization-server-backend</name>
<description>Authentication Server that supports OpenID Connect</description>
<url>http://maven.apache.org</url>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>16</java.version>
<testcontainers.version>1.16.0</testcontainers.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<avro.version>1.10.2</avro.version>
<jjwt.version>0.11.2</jjwt.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<!-- OAuth2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- jwk/jwt-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<!-- serialization -->
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>${avro.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- dev tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- metrics -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<!-- testing -->
<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>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
<imageName>rexijie/${project.artifactId}:${project.version}</imageName>
</configuration>
<executions>
<execution>
<id>build oci image</id>
<goals>
<goal>build-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy static assets</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/authorization-server-ui/target/dist</directory>
<includes>
<include>assets/</include>
<include>static/</include>
<include>favicon.ico</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>Copy Index HTML</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources/templates</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/authorization-server-ui/target/dist</directory>
<includes>
<include>index.html</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.rexijie.oauth.oauth2server.api;

import dev.rexijie.oauth.oauth2server.api.handlers.*;
import dev.rexijie.oauth.oauth2server.config.OAuth2Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.CacheControl;
Expand All @@ -19,12 +20,19 @@
@Component
public class BaseController {

private static final String OAUTH_BASE_PATH = "/oauth";
private final String OAUTH_BASE_PATH;
private static final String OIDC_BASE = "/openid";
private static final String API_BASE = "/api";
private static final String CLIENT_API_BASE = "/clients";
private static final String USER_API_BASE = "/users";

private final OAuth2Properties oauth2Props;

public BaseController(OAuth2Properties oAuth2Properties) {
this.oauth2Props = oAuth2Properties;
this.OAUTH_BASE_PATH = oauth2Props.server().basePath();
}

@Bean
RouterFunction<ServerResponse> appRoutes(LandingHandler landingHandler,
ClientEndpointHandler clientsHandler,
Expand All @@ -49,25 +57,35 @@ RouterFunction<ServerResponse> staticResources() {
var headers = request.exchange().getResponse().getHeaders();
headers.setCacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES));
return next.handle(request);
});
})
.and(
resources("/assets/**", new ClassPathResource("/assets/"))
.filter((request, next) -> {
var headers = request.exchange().getResponse().getHeaders();
headers.setCacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES));
return next.handle(request);
})
);
}

RouterFunction<ServerResponse> loginPage(LoginAndApprovalHandler loginAndApprovalHandler) {
return route()
.path("/", authorization -> authorization
.GET("login", loginAndApprovalHandler::indexPage)
.GET("approve", loginAndApprovalHandler::indexPage)
.POST("login", loginAndApprovalHandler::indexPage)
).build();
}

RouterFunction<ServerResponse> oAuthAuthorizationEndpoints(AuthorizationEndpointHandler authorizationEndpointHandler) {
return route()
.path(OAUTH_BASE_PATH, home -> home
.GET("/authorize", authorizationEndpointHandler::initiateAuthorization)
.POST("/authorize", authorizationEndpointHandler::authorizeRequest)
.path("authorize", authorize -> authorize
.GET(authorizationEndpointHandler::initiateAuthorization)
.POST(authorizationEndpointHandler::authorizeRequest)
)
.path("/approve", approve -> approve
.GET( authorizationEndpointHandler::approvalPage)
.POST( authorizationEndpointHandler::approve)
.GET(authorizationEndpointHandler::approvalPage)
.POST(authorizationEndpointHandler::approve)
)
)
.build();
Expand All @@ -91,8 +109,8 @@ RouterFunction<ServerResponse> oAuthTokenEndpoints(TokenEndpointHandler tokenHan
RouterFunction<ServerResponse> oidcEndpoint(OpenIdConnectHandler oidcHandler) {
return route()
.path(OIDC_BASE, home -> home
.GET(path("/.well-known/openid-configuration") ,oidcHandler::getOpenIdProperties)
.GET(path("/.well-known/jwks.json") ,oidcHandler::getJwkSet)
.GET(path("/.well-known/openid-configuration"), oidcHandler::getOpenIdProperties)
.GET(path("/.well-known/jwks.json"), oidcHandler::getJwkSet)
)
.build();
}
Expand All @@ -102,12 +120,12 @@ RouterFunction<ServerResponse> apiEndpoints(LandingHandler landingHandler,
UserEndpointHandler userHandler) {
return route()
.path(API_BASE, auth -> auth
.GET(path(""), landingHandler::homePage)
.GET(landingHandler::homePage)
.path(CLIENT_API_BASE, clients -> clients
.POST(path(""), clientsHandler::createClient)
.POST(clientsHandler::createClient)
)
.path(USER_API_BASE, users -> users
.POST(path(""), userHandler::saveUser)
.POST(userHandler::saveUser)
.GET("/{username}", userHandler::findUser)
)
).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.rexijie.oauth.oauth2server.api.domain.AuthorizationRequest;
import dev.rexijie.oauth.oauth2server.api.domain.OAuth2AuthorizationRequest;
import dev.rexijie.oauth.oauth2server.auth.AuthenticationStage;
import dev.rexijie.oauth.oauth2server.config.OAuth2Properties;
import dev.rexijie.oauth.oauth2server.error.OAuthError;
import dev.rexijie.oauth.oauth2server.model.dto.ClientDTO;
import dev.rexijie.oauth.oauth2server.services.ReactiveAuthorizationCodeServices;
Expand Down Expand Up @@ -59,11 +60,13 @@ public class AuthorizationEndpointHandler extends OAuthEndpointHandler {
private Resource index;
private final ReactiveAuthorizationCodeServices authorizationCodeServices;
private final ReactiveAuthenticationManager authenticationManager;
private final OAuth2Properties oAuth2Properties;

public AuthorizationEndpointHandler(ReactiveAuthorizationCodeServices reactiveAuthorizationCodeServices,
@Qualifier("userAuthenticationManager") ReactiveAuthenticationManager authenticationManager) {
@Qualifier("userAuthenticationManager") ReactiveAuthenticationManager authenticationManager, OAuth2Properties oAuth2Properties) {
this.authorizationCodeServices = reactiveAuthorizationCodeServices;
this.authenticationManager = authenticationManager;
this.oAuth2Properties = oAuth2Properties;
}

private AuthorizationRequest validateAuthorizationRequest(Principal principal,
Expand Down Expand Up @@ -95,8 +98,9 @@ public Mono<ServerResponse> initiateAuthorization(ServerRequest serverRequest) {
LOG.info("started session: {}", session.getId());
session.getAttributes().put(
AuthorizationRequest.AUTHORIZATION_SESSION_ATTRIBUTE, authorizationRequest);
return redirectTo(serverRequest, "/login");
return ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(index);
}))
.publishOn(Schedulers.boundedElastic())
.doOnError(throwable -> {
serverRequest.session().flatMap(WebSession::invalidate).subscribe();
LOG.error("error initialising authorization");
Expand Down Expand Up @@ -146,7 +150,7 @@ private Mono<ServerResponse> authorize(AuthorizationRequest authorizationRequest
return session.changeSessionId()
.thenReturn(session);
})
).flatMap(s -> redirectTo(request, "/oauth/approve"));
).flatMap(s -> redirectTo(request, "%s/approve".formatted(oAuth2Properties.server().basePath())));
}

private Mono<Authentication> authenticateRequest(AuthorizationRequest authorizationRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@ public class LoginAndApprovalHandler extends OAuthEndpointHandler {

@Value("classpath:/templates/index.html")
private Resource index;
@Value("classpath:/templates/index.html")
private Resource approve;

public Mono<ServerResponse> indexPage(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.TEXT_HTML)
.bodyValue(index);
}


public Mono<ServerResponse> approvalPage(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.TEXT_HTML)
.bodyValue(index);
}

public Mono<ServerResponse> redirectToLogin(ServerRequest request) {
return super.redirectTo(request, "/login");
}
Expand Down
Loading