Skip to content

Commit

Permalink
#39 - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Pieter Van Eeckhout committed Jun 27, 2023
1 parent 1d60e12 commit 23cb031
Show file tree
Hide file tree
Showing 23 changed files with 511 additions and 18 deletions.
6 changes: 2 additions & 4 deletions boot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ plugins {
id 'org.springframework.boot' version '3.1.0'
}

repositories {
mavenCentral()
}

dependencies {
implementation project(":trade-module")
implementation project(":user-module")
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.edpn.backend.application.controller;

import io.edpn.backend.user.application.controller.DefaultJwtAuthenticationController;
import io.edpn.backend.user.domain.service.JwtTokenService;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BootJwtAuthenticationController extends DefaultJwtAuthenticationController {

public BootJwtAuthenticationController(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, JwtTokenService jwtTokenUtil) {
super(passwordEncoder, userDetailsService, jwtTokenUtil);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package io.edpn.backend.application.controller;

import io.edpn.backend.trade.application.controller.DefaultTradeModuleController;
import io.edpn.backend.trade.domain.service.BestCommodityPriceService;
import io.edpn.backend.trade.application.controller.TradeModuleController;
import io.edpn.backend.trade.application.dto.CommodityMarketInfoResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@RestController
@RequiredArgsConstructor
public class BootTradeModuleController implements TradeModuleController {

private final BestCommodityPriceService bestCommodityPriceService;
public class BootTradeModuleController extends DefaultTradeModuleController {

@Override
public List<CommodityMarketInfoResponse> getBestCommodityPrice() {
return bestCommodityPriceService.getCommodityMarketInfo();
@Autowired
public BootTradeModuleController(BestCommodityPriceService bestCommodityPriceService) {
super(bestCommodityPriceService);
}
}
5 changes: 4 additions & 1 deletion boot/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
requires org.mybatis.spring;
requires spring.boot.autoconfigure;
requires spring.context;
requires io.edpn.backend.trade;
requires spring.web;
requires spring.beans;
requires spring.boot.starter.security;

requires io.edpn.backend.trade;
requires io.edpn.backend.user;

opens io.edpn.backend.application.controller to spring.core;
}
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ subprojects {
backendUtilVersion = '0.0.1-SNAPSHOT'
backendMybatisUtilVersion = '0.0.1-SNAPSHOT'
backendMessageProcessorLibVersion = '0.0.1-SNAPSHOT'
bucket4jCoreVersion = '7.6.0'
jjwtVersion='0.9.1'
jakartaVersion='6.0.0'
}

dependencies {
Expand All @@ -88,7 +91,9 @@ subprojects {
implementation "io.edpn.backend:backend-mybatis-util:${backendUtilVersion}"
implementation "io.edpn.backend:backend-messageprocessor-lib:${backendUtilVersion}"
implementation "org.springframework.boot:spring-boot-starter:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-security:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-jersey:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-integration:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
Expand All @@ -100,9 +105,12 @@ subprojects {
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
implementation "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}"
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
implementation "com.github.vladimir-bukhtoyarov:bucket4j-core:${bucket4jCoreVersion}"
implementation "io.jsonwebtoken:jjwt:${jjwtVersion}"
runtimeOnly "org.postgresql:postgresql:${postgresqlVersion}"
runtimeOnly "io.micrometer:micrometer-registry-prometheus:${prometheusVersion}"
compileOnly "org.projectlombok:lombok:${lombokVersion}"
compileOnly "jakarta.servlet:jakarta.servlet-api:${jakartaVersion}"
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
testCompileOnly "org.projectlombok:lombok:${lombokVersion}"
testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ rootProject.name = 'backend'
// modules
include 'boot'
include 'trade-module'
include 'user-module'
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.edpn.backend.trade.application.controller;

import io.edpn.backend.trade.application.dto.CommodityMarketInfoResponse;
import io.edpn.backend.trade.domain.controller.TradeModuleController;
import io.edpn.backend.trade.domain.service.BestCommodityPriceService;
import java.util.List;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
public class DefaultTradeModuleController implements TradeModuleController {

private final BestCommodityPriceService bestCommodityPriceService;

@Override
public List<CommodityMarketInfoResponse> getBestCommodityPrice() {
return bestCommodityPriceService.getCommodityMarketInfo();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.edpn.backend.trade.application.controller;
package io.edpn.backend.trade.domain.controller;

import io.edpn.backend.trade.application.dto.CommodityMarketInfoResponse;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down
1 change: 1 addition & 0 deletions trade-module/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@
exports io.edpn.backend.trade.application.dto;
exports io.edpn.backend.trade.application.service;
exports io.edpn.backend.trade.domain.service;
exports io.edpn.backend.trade.domain.controller;
}
41 changes: 41 additions & 0 deletions user-module/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
implementation 'org.liquibase:liquibase-core'
implementation 'io.jsonwebtoken:jjwt'
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core'

compileOnly 'jakarta.servlet:jakarta.servlet-api'
compileOnly 'org.projectlombok:lombok'

annotationProcessor 'org.projectlombok:lombok'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

extraJavaModuleInfo {
automaticModule("dependency-management-plugin-1.1.0.jar","dependency.management.plugin")
//automaticModule("tomlj-1.0.0.jar","tomlj")
automaticModule("jsr305-3.0.2.jar","jsr305")
automaticModule("snappy-java-1.1.8.4.jar","snappy.java")
automaticModule("liquibase-core-4.17.2.jar","liquibase.core")
automaticModule("jjwt-0.9.1.jar", "io.jsonwebtoken")
/*
automaticModule("jersey-media-json-jackson-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-container-servlet-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-spring6-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-container-servlet-core-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-bean-validation-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-server-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-client-3.1.1.jar", "jersy.jackson")
automaticModule("jersey-hk2-3.1.1.jar", "jersy.jackson")
automaticModule("hk2-3.0.3.jar", "jersy.jackson")
automaticModule("jersey-entity-filtering-3.1.1.jar", "jersy.jackson")
automaticModule("osgi-resource-locator-1.0.3.jar", "jersy.jackson")
automaticModule("javassist-3.29.0-GA.jar", "jersy.jackson")
automaticModule("jersey-common-3.1.1.jar", "jersy.jackson")*/

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.edpn.backend.user.application.controller;


import io.edpn.backend.user.domain.controller.AuthenticationController;
import io.edpn.backend.user.domain.service.JwtTokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

@RequiredArgsConstructor
public class DefaultJwtAuthenticationController implements AuthenticationController {

private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
private final JwtTokenService jwtTokenUtil;

@Override
public ResponseEntity<? extends AuthenticationResponse> createAuthenticationToken(AuthenticationRequest jsonAuthenticationRequest) throws BadCredentialsException {
try {
final UserDetails userDetails = userDetailsService
.loadUserByUsername(jsonAuthenticationRequest.getUsername());
if (passwordEncoder.matches(jsonAuthenticationRequest.getPassword(), userDetails.getPassword())) {
throw new BadCredentialsException("Incorrect username or password");
}

var response = io.edpn.backend.user.application.dto.AuthenticationResponse.builder()
.jwt(jwtTokenUtil.generateToken(userDetails))
.refreshToken(jwtTokenUtil.generateRefreshToken(userDetails))
.build();

return ResponseEntity.ok(response);
} catch (UsernameNotFoundException unfe) {
throw new BadCredentialsException("Incorrect username or password", unfe);
}
}

@Override
public ResponseEntity<? extends AuthenticationResponse> refreshToken(RefreshTokenRequest refreshTokenRequest) throws Exception {
String refreshToken = refreshTokenRequest.getRefreshToken();
String username = jwtTokenUtil.extractUsername(refreshToken);

final UserDetails userDetails = userDetailsService.loadUserByUsername(username);

if (jwtTokenUtil.validateToken(refreshToken, userDetails)) {
var response = io.edpn.backend.user.application.dto.AuthenticationResponse.builder()
.jwt(jwtTokenUtil.generateToken(userDetails))
.refreshToken(refreshToken) // do not send a new refresh token, need to log in again after 24 hours
.build();

return ResponseEntity.ok(response);
} else {
throw new Exception("Invalid refresh token");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.edpn.backend.user.application.dto;

import io.edpn.backend.user.domain.controller.AuthenticationController;
import lombok.Builder;
import lombok.Value;

@Value(staticConstructor = "of")
@Builder
public class AuthenticationResponse implements AuthenticationController.AuthenticationResponse {
String jwt;
String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.edpn.backend.user.application.service;

import io.edpn.backend.user.domain.service.JwtTokenService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class DefaultJwtTokenService implements JwtTokenService {

@Value("${jwt.secret:secret}")
private String secret;

@Override
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}

@Override
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}

@Override
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}

@Override
public Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}

@Override
public Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}

@Override
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}

@Override
public String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 1)) // 1 hours token validity
.signWith(SignatureAlgorithm.HS512, secret).compact();
}

@Override
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}

@Override
public String generateRefreshToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createRefreshToken(claims, userDetails.getUsername());
}

@Override
public String createRefreshToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) // 24 hours token validity
.signWith(SignatureAlgorithm.HS512, secret).compact();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.edpn.backend.user.configuration;

import io.edpn.backend.user.configuration.filter.JwtRequestFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
public class SecurityConfiguration {

@Bean
public SecurityFilterChain endpointSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
return http.build();
}

@Bean
public SecurityFilterChain jwtRequestFilterFilterChain(HttpSecurity http, JwtRequestFilter jwtRequestFilter) throws Exception {
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}


@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
Loading

0 comments on commit 23cb031

Please sign in to comment.