Browse Source

SessionManager using JWT

pull/1/head
Slixe 2 years ago
parent
commit
2056118468
12 changed files with 211 additions and 39 deletions
  1. +25
    -24
      build.gradle
  2. +1
    -0
      dero-benchmark-vue/src/App.vue
  3. +1
    -1
      dero-benchmark-vue/src/main.js
  4. +3
    -1
      dero-benchmark-vue/src/views/auth/Login.vue
  5. +3
    -2
      dero-benchmark-vue/src/views/auth/UBenchmarks.vue
  6. +11
    -1
      src/main/java/fr/slixe/benchmarks/Main.java
  7. +2
    -0
      src/main/java/fr/slixe/benchmarks/User.java
  8. +15
    -1
      src/main/java/fr/slixe/benchmarks/http/AuthMiddleware.java
  9. +7
    -3
      src/main/java/fr/slixe/benchmarks/http/controller/AuthController.groovy
  10. +136
    -0
      src/main/java/fr/slixe/benchmarks/service/SessionManager.java
  11. +1
    -1
      src/main/resources/config/app.config.groovy
  12. +6
    -5
      src/main/resources/config/routes.groovy

+ 25
- 24
build.gradle View File

@@ -19,37 +19,38 @@ sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
maven {
url 'http://wytrem.github.io/maven'
}
maven {
url 'https://paladin-framework.github.io/maven'
}
mavenCentral()
maven {
url 'http://wytrem.github.io/maven'
}
maven {
url 'https://paladin-framework.github.io/maven'
}
mavenCentral()
}

dependencies {
implementation(
'com.sparkjava:spark-core:2.8.0',
'net.sf.trove4j:trove4j:3.0.3',
'com.google.code.gson:gson:2.8.5',
'com.mashape.unirest:unirest-java:1.4.9',
'org.apache.commons:commons-lang3:3.8.1',
'org.apache.logging.log4j:log4j-core:2.10.0'
)
implementation('fr.litarvan.paladin:paladin-framework:1.1.0') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore-nio'
exclude group: 'org.apache.logging.log4j', module: 'log4j-core'
}
implementation(
'com.sparkjava:spark-core:2.8.0',
'net.sf.trove4j:trove4j:3.0.3',
'com.google.code.gson:gson:2.8.5',
'com.mashape.unirest:unirest-java:1.4.9',
'org.apache.commons:commons-lang3:3.8.1',
'org.apache.logging.log4j:log4j-core:2.10.0',
'com.auth0:java-jwt:3.10.2'
)

implementation('fr.litarvan.paladin:paladin-framework:1.1.0') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore-nio'
exclude group: 'org.apache.logging.log4j', module: 'log4j-core'
}
}

task fatJar(type: Jar) {
from {
configurations
.runtimeClasspath
.runtimeClasspath
.findAll { !it.name.endsWith('pom') }
.collect { it.isDirectory() ? it : zipTree(it) }
}
@@ -58,6 +59,6 @@ task fatJar(type: Jar) {
baseName = 'dero-benchmarks'

manifest {
attributes 'Main-Class': mainClassName
attributes 'Main-Class': mainClassName
}
}

+ 1
- 0
dero-benchmark-vue/src/App.vue View File

@@ -29,6 +29,7 @@ export default {
}
else {
localStorage.removeItem("token")
this.$router.push('/')
}
})
}


+ 1
- 1
dero-benchmark-vue/src/main.js View File

@@ -7,7 +7,7 @@ import 'material-design-icons-iconfont/dist/material-design-icons.css'

Vue.config.productionTip = false

Vue.prototype.$api = "http://localhost:8081"
Vue.prototype.$api = "http://localhost:8080"

Vue.use(Vuetify)
new Vue({


+ 3
- 1
dero-benchmark-vue/src/views/auth/Login.vue View File

@@ -25,7 +25,8 @@ export default {
},
mounted() {
let token = localStorage.getItem("token")
if (token != null && token.length == 86)

if (token != null)
{
this.$router.push("/unconfirmedBenchmarks")
}
@@ -40,6 +41,7 @@ export default {
password: this.password
})
}).then(result => result.json()).then(json => {
console.log(json)
let valid = json.token != null
this.alertType = valid ? "success" : "error"
this.alertMessage = valid ? "You are now logged in!" : json.message


+ 3
- 2
dero-benchmark-vue/src/views/auth/UBenchmarks.vue View File

@@ -62,16 +62,17 @@ export default {
},
mounted() {
let token = localStorage.getItem("token")
if (token == null || token.length != 86) //TODO must wait App.mounted()
if (token == null) //TODO must wait App.mounted()
{
this.$router.push("/login")
return
}
}

let headers = new Headers();
headers.append("Authorization", "Bearer " + token)

fetch(this.$api + "/api/unconfirmedBenchmarks", { headers: headers }).then(result => result.json()).then(json => {
console.log(json)
this.benchmarks = json
this.loading = false
})


+ 11
- 1
src/main/java/fr/slixe/benchmarks/Main.java View File

@@ -11,6 +11,7 @@ import fr.litarvan.paladin.Paladin;
import fr.litarvan.paladin.PaladinBuilder;
import fr.litarvan.paladin.PaladinConfig;
import fr.slixe.benchmarks.http.SparkHttpServer;
import fr.slixe.benchmarks.service.SessionManager;
public class Main
{
@@ -21,11 +22,20 @@ public class Main
Paladin paladin = PaladinBuilder.create(App.class)
.addModule(new MyModule())
.loadCommandLineArguments(args)
.setSessionManager(new SessionManager())
.build();
PaladinConfig config = paladin.getConfig();
SparkHttpServer httpServer = new SparkHttpServer(paladin, config.get("port", int.class));
SparkHttpServer httpServer = new SparkHttpServer(paladin, config.get("port", int.class));
String sessionSecret = config.get("secret");
if (sessionSecret.isEmpty() || sessionSecret.length() < 6) {
log.error("The secret key for Session Manager is empty or low!! Exiting...");
return;
}
((SessionManager) paladin.getSessionManager()).init(paladin, sessionSecret);
if (config.get("enableSSL", boolean.class))
{
log.info("Enabling SSL...");


+ 2
- 0
src/main/java/fr/slixe/benchmarks/User.java View File

@@ -3,7 +3,9 @@ package fr.slixe.benchmarks;
public class User {

private final String username;
@JsonIgnore
private final String hashedPassword;
@JsonIgnore
private final String salt;

public User(String username, String hashedPassword, String salt)


+ 15
- 1
src/main/java/fr/slixe/benchmarks/http/AuthMiddleware.java View File

@@ -1,7 +1,11 @@
package fr.slixe.benchmarks.http;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.litarvan.paladin.AfterEvent;
import fr.litarvan.paladin.BeforeEvent;
import fr.litarvan.paladin.Session;
import fr.litarvan.paladin.http.Middleware;
import fr.litarvan.paladin.http.Request;
import fr.litarvan.paladin.http.Response;
@@ -11,12 +15,22 @@ import fr.slixe.benchmarks.User;

public class AuthMiddleware extends Middleware
{
private static final Logger log = LoggerFactory.getLogger("Auth Middleware");

@Override
public void before(BeforeEvent event, Request request, Response response, Route route) throws RequestException
{
if (request.getSession().get(User.class) == null) {
log.info("{}", request.getMethod());
log.info(request.getIp());
log.info(request.getUri());
Session session = request.getSession();
if (session == null || session.get(User.class) == null) {
log.info("NOT LOGGED IN");
throw new UnauthorizedOperationException();
}
else {
log.info("{}", session.get(User.class));
}
}

@Override


+ 7
- 3
src/main/java/fr/slixe/benchmarks/http/controller/AuthController.groovy View File

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory

import com.google.inject.Inject

import fr.litarvan.paladin.Paladin
import fr.litarvan.paladin.Session
import fr.litarvan.paladin.http.Controller
import fr.litarvan.paladin.http.routing.JsonBody
@@ -12,6 +13,7 @@ import fr.litarvan.paladin.http.routing.RequestParams
import fr.slixe.benchmarks.User
import fr.slixe.benchmarks.http.InvalidParameterException
import fr.slixe.benchmarks.service.AuthService
import fr.slixe.benchmarks.service.SessionManager

public class AuthController extends Controller {

@@ -20,9 +22,12 @@ public class AuthController extends Controller {
@Inject
private AuthService authService

@Inject
private Paladin paladin;

@JsonBody
@RequestParams(required = ["username", "password"])
def login(String username, String password, Session session)
def login(String username, String password)
{
if (password.length() > 64) {
throw new InvalidParameterException("Password is too long")
@@ -36,8 +41,7 @@ public class AuthController extends Controller {

log.info(String.format("User %s is now logged in.", user.getUsername()))

session[User] = user

Session session = ((SessionManager) paladin.getSessionManager()).createSession(user)
[
token: session.token
]


+ 136
- 0
src/main/java/fr/slixe/benchmarks/service/SessionManager.java View File

@@ -0,0 +1,136 @@
package fr.slixe.benchmarks.service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import fr.litarvan.paladin.ISessionManager;
import fr.litarvan.paladin.Paladin;
import fr.litarvan.paladin.Session;
import fr.litarvan.paladin.http.Header;
import fr.litarvan.paladin.http.Request;
import fr.litarvan.paladin.http.Response;
import fr.slixe.benchmarks.JsonIgnore;
import fr.slixe.benchmarks.User;

public class SessionManager implements ISessionManager {

private final Gson gson = new GsonBuilder().addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(JsonIgnore.class) != null;
}

@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();

private Paladin paladin;
private Algorithm algorithm;
private long expirationDelay;

public SessionManager()
{
this.expirationDelay = TimeUnit.DAYS.toSeconds(30);
}

public void init(Paladin paladin, String secret)
{
this.paladin = paladin;
this.algorithm = Algorithm.HMAC256(secret);
}

@Override
public Session get(Request request, Response response)
{
String token = request.getHeaderValue(Header.AUTHORIZATION);

if (token != null)
{
if (token.startsWith("Bearer") && token.length() > 7)
{
token = token.substring(7);
}

try {
JWTVerifier verifier = JWT.require(this.algorithm).build();
DecodedJWT decodedToken = verifier.verify(token);
token = decodedToken.getToken();

return getSession(decodedToken);
} catch (SignatureVerificationException | TokenExpiredException e) {
e.printStackTrace();
}
}

return null;
}

public boolean isValidToken(Request request)
{
String token = request.getHeaderValue(Header.AUTHORIZATION);
DecodedJWT decodedToken = null;

if (token != null)
{
if (token.startsWith("Bearer") && token.length() > 7)
{
token = token.substring(7);
}

try {
JWTVerifier verifier = JWT.require(this.algorithm).build();
decodedToken = verifier.verify(token);
} catch (SignatureVerificationException | TokenExpiredException ignored) {}
}

return decodedToken != null;
}

@Override
public long getExpirationDelay()
{
return expirationDelay;
}

@Override
public void setExpirationDelay(long expirationDelay)
{
this.expirationDelay = expirationDelay;
}

private Session getSession(DecodedJWT jwt)
{
long expiresAt = expirationDelay <= 0 ? -1 : LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) * 1000 + expirationDelay;
Session session = new Session(expiresAt, jwt.getToken());

session.putAt(User.class, gson.fromJson(jwt.getClaim("user").asString(), User.class));

return session;
}

public Session createSession(User user)
{
long expiresAt = expirationDelay <= 0 ? -1 : LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) * 1000 + expirationDelay;
String token = JWT.create().withIssuer(paladin.getAppInfo().name())
.withClaim("user", gson.toJson(user))
.withIssuedAt(new Date(System.currentTimeMillis())).withExpiresAt(new Date(expiresAt))
.sign(this.algorithm);

return new Session(expiresAt, token);
}
}

+ 1
- 1
src/main/resources/config/app.config.groovy View File

@@ -8,7 +8,7 @@ import fr.slixe.benchmarks.http.controller.AuthController
import fr.slixe.benchmarks.http.controller.MainController
[
sessionDuration: TimeUnit.DAYS.toMillis(61), // 2 Months
sessionDuration: TimeUnit.DAYS.toMillis(2), // 2 days
/**
* The app controllers, call them whatever you want to


+ 6
- 5
src/main/resources/config/routes.groovy View File

@@ -1,6 +1,6 @@
package config;
group '/api', {
group '/api', {
get '/benchmarks', 'main:benchmarks'
post '/submit', 'main:submit'
@@ -9,15 +9,16 @@ group '/api', {
post '/confirm'
post '/delete'
}, [
action: 'main',
middleware: 'auth'
action: 'main',
middleware: 'auth'
]
}
group '/api/auth', { //not recognized in group /api
group '/api/auth', {
//not recognized in group /api
post '/validate'
post '/login'
post '/logout'
}, [
action: 'auth'
action: 'auth'
]

Loading…
Cancel
Save