Skip to content

Commit 7783d74

Browse files
committed
Keycloak: added authentication
1 parent 6f20aec commit 7783d74

File tree

8 files changed

+193
-25
lines changed

8 files changed

+193
-25
lines changed

deploy/docker-compose.yml

+51-23
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
version: '3'
22
services:
3-
backend:
4-
image: gufalcon/http-server:latest
5-
restart: always
6-
depends_on:
7-
- db
8-
ports:
9-
- "1280:8080"
10-
- "12443:8443"
11-
environment:
12-
- HTTP_PORT=8080
13-
- HTTP_HOST=0.0.0.0
14-
- DB_DRIVER=mysql
15-
- DB_SERVER=db
16-
- DB_PORT=3306
17-
- DB_NAME=httpserver
18-
- DB_USER=httpserver
19-
- DB_PASSWORD=DP5V7hi7xgknaH39lDTX
20-
labels:
21-
- "traefik.enable=true"
22-
- "traefik.http-server.port=8080"
23-
- "traefik.http-server.backend=http-server"
24-
- "traefik.http-server.frontend.rule=Host:http-server.unterrainer.info"
25-
- "traefik.http-server.frontend.entryPoints=http,https"
3+
# backend:
4+
# image: gufalcon/http-server:latest
5+
# restart: always
6+
# depends_on:
7+
# - db
8+
# ports:
9+
# - "1280:8080"
10+
# - "12443:8443"
11+
# environment:
12+
# - HTTP_PORT=8080
13+
# - HTTP_HOST=0.0.0.0
14+
# - DB_DRIVER=mysql
15+
# - DB_SERVER=db
16+
# - DB_PORT=3306
17+
# - DB_NAME=httpserver
18+
# - DB_USER=httpserver
19+
# - DB_PASSWORD=DP5V7hi7xgknaH39lDTX
20+
# labels:
21+
# - "traefik.enable=true"
22+
# - "traefik.http-server.port=8080"
23+
# - "traefik.http-server.backend=http-server"
24+
# - "traefik.http-server.frontend.rule=Host:http-server.unterrainer.info"
25+
# - "traefik.http-server.frontend.entryPoints=http,https"
2626

2727
db:
2828
image: mariadb:latest
@@ -37,6 +37,34 @@ services:
3737
volumes:
3838
- "/app/data/http-server/mysql-data/db:/var/lib/mysql"
3939

40+
keycloak_db:
41+
image: mariadb:latest
42+
restart: always
43+
ports:
44+
- 14310:3306
45+
environment:
46+
- MYSQL_ROOT_PASSWORD=3YazHzbLuzChlwroXLSm5I
47+
- MYSQL_DATABASE=keycloak
48+
- MYSQL_USER=keycloak
49+
- MYSQL_PASSWORD=8KNM3flMpEyCYkFw5wrGoCN3
50+
volumes:
51+
- "/app/data/keycloak/mysql-data/db:/var/lib/mysql"
52+
53+
keycloak:
54+
image: quay.io/keycloak/keycloak:latest
55+
environment:
56+
DB_VENDOR: mariadb
57+
DB_ADDR: keycloak_db
58+
DB_DATABASE: keycloak
59+
DB_USER: keycloak
60+
DB_PASSWORD: 8KNM3flMpEyCYkFw5wrGoCN3
61+
KEYCLOAK_USER: admin
62+
KEYCLOAK_PASSWORD: cTWJiYZDutP6tYxXnv2yg0A
63+
ports:
64+
- 8080:8080
65+
depends_on:
66+
- keycloak_db
67+
4068
networks:
4169
default:
4270
external:

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
<artifactId>jetty-server</artifactId>
5858
<version>9.4.30.v20200611</version>
5959
</dependency>
60+
<dependency>
61+
<groupId>org.keycloak</groupId>
62+
<artifactId>keycloak-core</artifactId>
63+
<version>11.0.2</version>
64+
</dependency>
6065
</dependencies>
6166

6267
</project>

src/main/java/info/unterrainer/commons/httpserver/HttpServer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.eclipse.jetty.server.Server;
1515
import org.eclipse.jetty.server.ServerConnector;
1616

17+
import info.unterrainer.commons.httpserver.accessmanager.HttpAccessManager;
1718
import info.unterrainer.commons.httpserver.daos.DaoTransactionManager;
1819
import info.unterrainer.commons.httpserver.enums.Attribute;
1920
import info.unterrainer.commons.httpserver.exceptions.HttpException;
@@ -96,7 +97,7 @@ private void create() {
9697
server.setConnectors(new ServerConnector[] { connector });
9798

9899
javalin = Javalin.create(config -> {
99-
config.server(() -> server).enableCorsForAllOrigins();
100+
config.server(() -> server).accessManager(new HttpAccessManager()).enableCorsForAllOrigins();
100101
}).start(config.port());
101102

102103
javalin.before(ctx -> ctx.attribute(Attribute.JAVALIN_SERVER, this));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package info.unterrainer.commons.httpserver.accessmanager;
2+
3+
import io.javalin.core.security.Role;
4+
5+
public enum DefaultRoles implements Role {
6+
PUBLIC, AUTHENTICATED;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package info.unterrainer.commons.httpserver.accessmanager;
2+
3+
import java.io.IOException;
4+
import java.io.UncheckedIOException;
5+
import java.net.URI;
6+
import java.net.URISyntaxException;
7+
import java.net.http.HttpClient;
8+
import java.net.http.HttpRequest;
9+
import java.net.http.HttpResponse;
10+
import java.net.http.HttpResponse.BodyHandlers;
11+
import java.security.PublicKey;
12+
import java.util.Set;
13+
14+
import org.eclipse.jetty.http.HttpHeader;
15+
import org.keycloak.TokenVerifier;
16+
import org.keycloak.common.VerificationException;
17+
import org.keycloak.representations.AccessToken;
18+
import org.keycloak.representations.idm.PublishedRealmRepresentation;
19+
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
22+
import info.unterrainer.commons.httpserver.exceptions.ForbiddenException;
23+
import info.unterrainer.commons.httpserver.exceptions.UnauthorizedException;
24+
import io.javalin.core.security.AccessManager;
25+
import io.javalin.core.security.Role;
26+
import io.javalin.http.Context;
27+
import io.javalin.http.Handler;
28+
29+
public class HttpAccessManager implements AccessManager {
30+
31+
private PublicKey publicKey;
32+
33+
public HttpAccessManager() {
34+
try {
35+
String authUrl = "http://localhost:8080" + "/auth/realms/" + "nexus";
36+
37+
HttpClient client = HttpClient.newHttpClient();
38+
HttpRequest request = HttpRequest.newBuilder().uri(new URI(authUrl)).build();
39+
ObjectMapper objectMapper = new ObjectMapper();
40+
41+
client.sendAsync(request, BodyHandlers.ofString()).thenApply(HttpResponse::body).thenAccept(body -> {
42+
try {
43+
publicKey = objectMapper.readValue(body, PublishedRealmRepresentation.class).getPublicKey();
44+
} catch (IOException e) {
45+
throw new UncheckedIOException(e);
46+
}
47+
}).join();
48+
} catch (URISyntaxException e) {
49+
throw new IllegalStateException(e);
50+
}
51+
52+
}
53+
54+
@Override
55+
public void manage(Handler handler, Context ctx, Set<Role> permittedRoles) throws Exception {
56+
57+
checkAccess(ctx, permittedRoles);
58+
59+
handler.handle(ctx);
60+
}
61+
62+
private void checkAccess(Context ctx, Set<Role> permittedRoles) {
63+
64+
if (permittedRoles.isEmpty() || permittedRoles.contains(DefaultRoles.PUBLIC)) {
65+
return;
66+
}
67+
68+
String authorizationHeader = ctx.header(HttpHeader.AUTHORIZATION.asString());
69+
TokenVerifier<AccessToken> accessToken = TokenVerifier.create(authorizationHeader, AccessToken.class);
70+
accessToken.publicKey(publicKey);
71+
72+
try {
73+
accessToken.verifySignature();
74+
} catch (VerificationException e) {
75+
throw new UnauthorizedException();
76+
}
77+
78+
try {
79+
accessToken.verify();
80+
} catch (VerificationException e) {
81+
throw new ForbiddenException();
82+
}
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package info.unterrainer.commons.httpserver.exceptions;
2+
3+
public class ForbiddenException extends HttpException {
4+
5+
private static final long serialVersionUID = -9128226690837369251L;
6+
7+
public static final int HTTP_STATUS = 403;
8+
public static final String HTTP_TEXT = "Forbidden";
9+
10+
public ForbiddenException(final String message, final Throwable cause) {
11+
super(HTTP_STATUS, HTTP_TEXT, message, cause);
12+
}
13+
14+
public ForbiddenException(final String message) {
15+
super(HTTP_STATUS, HTTP_TEXT, message);
16+
}
17+
18+
public ForbiddenException() {
19+
super(HTTP_STATUS, HTTP_TEXT);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package info.unterrainer.commons.httpserver.exceptions;
2+
3+
public class UnauthorizedException extends HttpException {
4+
5+
private static final long serialVersionUID = -8429546406728629818L;
6+
7+
public static final int HTTP_STATUS = 401;
8+
public static final String HTTP_TEXT = "Unauthorized";
9+
10+
public UnauthorizedException(final String message, final Throwable cause) {
11+
super(HTTP_STATUS, HTTP_TEXT, message, cause);
12+
}
13+
14+
public UnauthorizedException(final String message) {
15+
super(HTTP_STATUS, HTTP_TEXT, message);
16+
}
17+
18+
public UnauthorizedException() {
19+
super(HTTP_STATUS, HTTP_TEXT);
20+
}
21+
}

src/test/java/info/unterrainer/commons/httpserver/scripts/LocalTestServer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import javax.persistence.EntityManagerFactory;
44

55
import info.unterrainer.commons.httpserver.HttpServer;
6+
import info.unterrainer.commons.httpserver.accessmanager.DefaultRoles;
67
import info.unterrainer.commons.httpserver.daos.JpqlDao;
78
import info.unterrainer.commons.httpserver.daos.JpqlTransactionManager;
89
import info.unterrainer.commons.httpserver.enums.Endpoint;
@@ -42,7 +43,7 @@ public static void main(final String[] args) throws Exception {
4243
private static void startServer() {
4344
HttpServer server = HttpServer.builder().applicationName("local-test-server").build();
4445

45-
server.get("/status", new StatusHandler(mapper));
46+
server.get("/status", new StatusHandler(mapper), DefaultRoles.AUTHENTICATED);
4647
server.handlerGroupFor(TestJpa.class, TestJson.class, new JpqlTransactionManager(emf))
4748
.path("/test")
4849
.endpoints(Endpoint.ALL)

0 commit comments

Comments
 (0)