Skip to content

Commit

Permalink
fix: small security & typo changes
Browse files Browse the repository at this point in the history
  • Loading branch information
thisdudkin committed Sep 12, 2024
1 parent 8d7679b commit bd563b3
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ public class Book {
@Column(name = "author", nullable = false)
private String author;

@Setter
@Column(name = "available", nullable = false)
private Boolean available = true;

@Setter
@Column(name = "appearedUtc", nullable = false, updatable = false)
private Instant appearedUtc;
Expand All @@ -54,14 +50,13 @@ protected void onCreate() {
public Book() {
}

public Book(Integer id, String isbn, String title, String genre, String description, String author, Boolean available, Instant appearedUtc) {
public Book(Integer id, String isbn, String title, String genre, String description, String author, Instant appearedUtc) {
this.id = id;
this.isbn = isbn;
this.title = title;
this.genre = genre;
this.description = description;
this.author = author;
this.available = available;
this.appearedUtc = appearedUtc;
}

Expand All @@ -74,7 +69,6 @@ public String toString() {
.append("genre", genre)
.append("description", description)
.append("author", author)
.append("available", available)
.append("appearedUtc", appearedUtc)
.toString();
}
Expand Down
58 changes: 58 additions & 0 deletions library-api-gateway/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>

<artifactId>library-api-gateway</artifactId>
<packaging>jar</packaging>
<description>Library API Gateway</description>

<parent>
<groupId>dev.earlspilner</groupId>
<artifactId>library-api</artifactId>
<version>1.1.0</version>
</parent>

<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- Spring Cloud Netflix Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>

<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.earlspilner.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* @author Alexander Dudkin
*/
@EnableDiscoveryClient
@SpringBootApplication
public class ApiGatewayApplication {

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dev.earlspilner.gateway.config;

import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* @author Alexander Dudkin
*/
@Component
@RefreshScope
public class AuthenticationFilter implements GatewayFilter {

private final RouterValidator routerValidator;
private final JwtUtil jwtUtil;

@Autowired
public AuthenticationFilter(RouterValidator routerValidator, JwtUtil jwtUtil) {
this.routerValidator = routerValidator;
this.jwtUtil = jwtUtil;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();

if (routerValidator.isSecured.test(request)) {
if (this.isAuthMissing(request)) {
return this.onError(exchange, HttpStatus.UNAUTHORIZED);
}

final String token = this.getAuthHeader(request);

if (jwtUtil.isInvalid(token)) {
return this.onError(exchange, HttpStatus.FORBIDDEN);
}

this.updateRequest(exchange, token);
}

return chain.filter(exchange);
}

private Mono<Void> onError(ServerWebExchange exchange, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
return response.setComplete();
}

private String getAuthHeader(ServerHttpRequest request) {
String header = request.getHeaders().getOrEmpty("Authorization").get(0);
return header.startsWith("Bearer ") ? header.substring(7) : header;
}

private boolean isAuthMissing(ServerHttpRequest request) {
return !request.getHeaders().containsKey("Authorization");
}

private void updateRequest(ServerWebExchange exchange, String token) {
Claims claims = jwtUtil.getAllClaimsFromToken(token);
exchange.getRequest().mutate()
.header("username", String.valueOf(claims.get("username")))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dev.earlspilner.gateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Alexander Dudkin
*/
@Configuration
@EnableHystrix
public class GatewayConfig {

static final String USERS_SERVICE = "http://localhost:9091";
static final String AUTH_SERVER = "http://localhost:6969";
static final String BOOKS_SERVICE = "http://localhost:9092";
static final String LOAN_SERVICE = "http://localhost:9093";
static final String LIBRARY_SERVICE = "http://localhost:9094";

private final AuthenticationFilter filter;

@Autowired
public GatewayConfig(AuthenticationFilter filter) {
this.filter = filter;
}

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("users-service", r -> r.path("/api/users/**")
.filters(f -> f.filter(filter))
.uri(USERS_SERVICE))
.route("auth-server", r -> r.path("/api/auth/**")
.filters(f -> f.filter(filter))
.uri(AUTH_SERVER))
.route("books-service", r -> r.path("/api/books/**")
.filters(f -> f.filter(filter))
.uri(BOOKS_SERVICE))
.route("loan-service", r -> r.path("/api/loans/**")
.filters(f -> f.filter(filter))
.uri(LOAN_SERVICE))
.route("library-service", r -> r.path("/api/library/**")
.filters(f -> f.filter(filter))
.uri(LIBRARY_SERVICE))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dev.earlspilner.gateway.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Base64;
import java.util.Date;

/**
* @author Alexander Dudkin
*/
@Component
public class JwtUtil {

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

private Key key;

@PostConstruct
protected void init() {
byte[] keyBytes = Base64.getDecoder().decode(jwtSecret.getBytes());
this.key = Keys.hmacShaKeyFor(keyBytes);
}

public Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

private boolean isTokenExpired(String token) {
return this.getAllClaimsFromToken(token).getExpiration().before(new Date());
}

public boolean isInvalid(String token) {
return this.isTokenExpired(token);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.earlspilner.gateway.config;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.function.Predicate;

/**
* @author Alexander Dudkin
*/
@Component
public class RouterValidator {

public static final List<String> openApiEndpoints = List.of(
"/api/auth/login",
"/api/auth/refresh",
"/api/users"
);

public Predicate<ServerHttpRequest> isSecured = request -> openApiEndpoints.stream()
.noneMatch(uri -> request.getURI().getPath().contains(uri));

}
6 changes: 6 additions & 0 deletions library-api-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spring:
application:
name: api-gateway

server:
port: 8080
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface LibraryApi {
ResponseEntity<BookRecordDto> addBootRecord(BookRecordDto dto);
ResponseEntity<BookRecordDto> getBookRecord(Integer bookId);
ResponseEntity<BookRecordDto> updateBookRecord(Integer bookId, BookRecordDto dto);
ResponseEntity<Void> deleteBookRecord(Integer bookId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ public ResponseEntity<BookRecordDto> updateBookRecord(@PathVariable Integer book
return new ResponseEntity<>(libraryService.updateBookRecord(bookId, dto), HttpStatus.OK);
}

@Override
@DeleteMapping("/{bookId}")
public ResponseEntity<Void> deleteBookRecord(@PathVariable Integer bookId) {
libraryService.deleteBookRecord(bookId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public interface LibraryService {
BookRecordDto addBookRecord(BookRecordDto dto);
BookRecordDto getBookRecord(Integer id);
BookRecordDto updateBookRecord(Integer bookId, BookRecordDto dto);
void deleteBookRecord(Integer bookId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ public BookRecordDto updateBookRecord(Integer bookId, BookRecordDto dto) {
return bookRecordMapper.toDto(bookRecordRepository.save(bookRecord));
}

@Override
public void deleteBookRecord(Integer bookId) {
bookRecordRepository.deleteById(bookId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
* @author Alexander Dudkin
*/
public interface LoanRepository extends JpaRepository<Loan, Integer> {
Optional<Loan> findByUserIdAndBookIdAndReturnedAtIsNull(Integer bookId, Integer userId);
Optional<Loan> findByBookIdAndUserIdAndReturnedAtIsNull(Integer bookId, Integer userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public LoanDto returnBook(Integer bookId, HttpServletRequest request) {
}

UserDto userDto = userClient.getUser(jwtCore.getUsernameFromToken(jwtCore.getTokenFromRequest(request)));
Loan loan = loanRepository.findByUserIdAndBookIdAndReturnedAtIsNull(bookId, userDto.id())
Loan loan = loanRepository.findByBookIdAndUserIdAndReturnedAtIsNull(bookId, userDto.id())
.orElseThrow(() -> new LoanNotFoundException("Loan not found with bookId '" + bookId + "' and userId '" + userDto.id() + "'"));
loan.setReturnedAt(Instant.now());
libraryClient.setBookStatus(bookId, new BookRecordDto(null, IN_LIBRARY));
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<module>library-api-library-service</module>
<module>library-api-discovery-server</module>
<module>library-api-loan-service</module>
<module>library-api-gateway</module>
</modules>

<groupId>dev.earlspilner</groupId>
Expand Down

0 comments on commit bd563b3

Please sign in to comment.