Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
build:
runs-on: ubuntu-latest
env:
SPRING_BASE_COMMONS_VERSION: 2.3.0
SPRING_BASE_COMMONS_VERSION: 2.4.1
steps:
- uses: actions/checkout@v4
- name: Set up JDK 25
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ENV APP_NAME=app.jar
ENV DEPS_FILE=deps.info

# Change this when there is an update
ENV SPRING_BASE_COMMONS_VERSION=2.3.0
ENV SPRING_BASE_COMMONS_VERSION=2.4.1

# Clone the spring-base-commons repository
RUN git clone --depth 1 --branch ${SPRING_BASE_COMMONS_VERSION} https://github.com/vulinh64/spring-base-commons.git
Expand Down
2 changes: 1 addition & 1 deletion create-data-classes.cmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@echo off

SET SPRING_BASE_COMMONS_VERSION=2.3.0
SET SPRING_BASE_COMMONS_VERSION=2.4.1

IF EXIST .\build\spring-base-commons rmdir /s /q .\build\spring-base-commons

Expand Down
2 changes: 1 addition & 1 deletion create-data-classes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -e

SPRING_BASE_COMMONS_VERSION=2.3.0
SPRING_BASE_COMMONS_VERSION=2.4.1

if [ -d "./build/spring-base-commons" ]; then
rm -rf ./build/spring-base-commons
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
<!-- this should be fixed by Spring in the future -->
<netty.version>4.2.9.Final</netty.version>

<spring-base-commons.version>2.3.0</spring-base-commons.version>
<spring-base-commons.version>2.4.1</spring-base-commons.version>
</properties>

<dependencies>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/vulinh/SpringBaseProjectApplication.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.vulinh;

import com.vulinh.aspect.ExecutionTimeAspect;
import com.vulinh.configuration.AuditorConfiguration;
import com.vulinh.configuration.data.ApplicationProperties;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode;
Expand All @@ -18,6 +20,7 @@
@EnableConfigurationProperties(ApplicationProperties.class)
@EnableAsync
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Import(ExecutionTimeAspect.class)
class SpringBaseProjectApplication {

static void main(String[] args) {
Expand Down
30 changes: 22 additions & 8 deletions src/main/java/com/vulinh/configuration/SecurityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
import com.vulinh.configuration.data.ApplicationProperties.SecurityProperties;
import com.vulinh.data.constant.UserRole;
import com.vulinh.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
Expand All @@ -24,13 +25,17 @@
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExceptionResolver;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@Slf4j
public class SecurityConfiguration {

// One of the best and the most elegant ways to handle exceptions in Spring Security filters
private final HandlerExceptionResolver handlerExceptionResolver;

static final String ROLE_ADMIN_NAME = UserRole.ADMIN.name();

@Bean
Expand All @@ -47,7 +52,6 @@ public SecurityFilterChain securityFilterChain(
xssConfig -> xssConfig.headerValue(HeaderValue.ENABLED_MODE_BLOCK))
.contentSecurityPolicy(cps -> cps.policyDirectives("script-src 'self'")))
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.sessionManagement(
sessionManagementConfigurer ->
sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Expand All @@ -57,12 +61,17 @@ public SecurityFilterChain securityFilterChain(
.cors(corsConfigurer -> corsConfigurer.configurationSource(createCorsFilter()))
.oauth2ResourceServer(
oAuth2ResourceServerProperties ->
oAuth2ResourceServerProperties.jwt(
jwtConfigurer ->
jwtConfigurer.jwtAuthenticationConverter(
jwt ->
JwtUtils.parseAuthoritiesByCustomClaims(
jwt, security.clientName()))))
oAuth2ResourceServerProperties
// Return something to client rather than a blank 403 page
.accessDeniedHandler(this::delegateToHandlerExceptionResolver)
// Return something to client rather than a blank 401 page
.authenticationEntryPoint(this::delegateToHandlerExceptionResolver)
.jwt(
jwtConfigurer ->
jwtConfigurer.jwtAuthenticationConverter(
jwt ->
JwtUtils.parseAuthoritiesByCustomClaims(
jwt, security.clientName()))))
.build();
}

Expand All @@ -86,6 +95,11 @@ CorsConfigurationSource createCorsFilter() {
return source;
}

private void delegateToHandlerExceptionResolver(
HttpServletRequest request, HttpServletResponse response, Exception exception) {
handlerExceptionResolver.resolveException(request, response, null, exception);
}

static void configureAuthorizeHttpRequestCustomizer(
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry
authorizeHttpRequestsCustomizer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import module java.base;

import com.vulinh.controller.api.SubscriptionAPI;
import com.vulinh.service.event.UserSubscriptionService;
import com.vulinh.service.UserSubscriptionService;
import com.vulinh.utils.ResponseUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class AuthorizationException extends ApplicationException {
/// @return A new [AuthorizationException] instance
public static AuthorizationException invalidAuthorization(Object... args) {
return invalidAuthorization(
"Invalid user authorization", ServiceErrorCode.MESSAGE_INVALID_AUTHORIZATION, args);
"Invalid user authorization", ServiceErrorCode.MESSAGE_INVALID_AUTHENTICATION, args);
}

/// Creates an [AuthorizationException] with a custom message and error code.
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/vulinh/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import com.vulinh.data.dto.response.GenericResponse.ResponseCreator;
import com.vulinh.locale.LocalizationSupport;
import com.vulinh.locale.ServiceErrorCode;
import com.vulinh.utils.validator.ApplicationError;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand Down Expand Up @@ -112,6 +114,19 @@ GenericResponse<Object> handleKeycloakUserDisabledException(
return logAndReturn(keycloakUserDisabledException);
}

@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
GenericResponse<Object> handleInvalidBearerTokenException(
AuthenticationException authenticationException) {
return securityError(ServiceErrorCode.MESSAGE_INVALID_AUTHENTICATION, authenticationException);
}

@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
GenericResponse<Object> handleAccessDeniedException(AccessDeniedException accessDeniedException) {
return securityError(ServiceErrorCode.MESSAGE_INSUFFICIENT_PERMISSION, accessDeniedException);
}

static GenericResponse<Object> badRequestBody(String additionalMessage) {
return GenericResponse.builder()
.errorCode(ServiceErrorCode.MESSAGE_INVALID_BODY_REQUEST.getErrorCode())
Expand All @@ -132,4 +147,11 @@ static GenericResponse<Object> stackTraceAndReturn(ApplicationException applicat

return ResponseCreator.toError(applicationException);
}

static GenericResponse<Object> securityError(
ApplicationError applicationError, Throwable throwable) {
log.info(throwable.getMessage(), throwable);

return ResponseCreator.toError(applicationError);
}
}
3 changes: 2 additions & 1 deletion src/main/java/com/vulinh/locale/ServiceErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum ServiceErrorCode implements ApplicationError {
MESSAGE_INVALID_CATEGORY_SLUG("M3002"),
MESSAGE_DEFAULT_CATEGORY_IMMORTAL("M3003"),

MESSAGE_INVALID_AUTHORIZATION("M9102"),
MESSAGE_INVALID_AUTHENTICATION("M9401"),
MESSAGE_INSUFFICIENT_PERMISSION("M9403"),
MESSAGE_INVALID_BODY_REQUEST("M9106"),
MESSAGE_INVALID_OWNER_OR_NO_RIGHT("M9107"),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.vulinh.service.event;
package com.vulinh.service;

import module java.base;

import com.vulinh.exception.KeycloakUserDisabledException;
import com.vulinh.service.event.EventService;
import com.vulinh.service.keycloak.KeycloakAdminClientService;
import com.vulinh.service.post.PostValidationService;
import com.vulinh.utils.SecurityUtils;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/vulinh/service/event/EventService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import module java.base;

import com.vulinh.annotation.ExecutionTime;
import com.vulinh.configuration.data.ApplicationProperties;
import com.vulinh.configuration.data.ApplicationProperties.TopicProperties;
import com.vulinh.data.dto.response.KeycloakUserResponse;
Expand Down Expand Up @@ -29,13 +30,15 @@ public class EventService {

final ApplicationProperties applicationProperties;

@ExecutionTime
public void sendNewPostEvent(Post post, UserBasicResponse actionUser) {
sendMessageInternal(
applicationProperties.messageTopic().newPost(),
actionUser,
EVENT_MAPPER.toNewPostEvent(post));
}

@ExecutionTime
public void sendSubscribeToUserEvent(
UserBasicResponse basicActionUser, KeycloakUserResponse subscribedUser) {
sendMessageInternal(
Expand All @@ -44,13 +47,15 @@ public void sendSubscribeToUserEvent(
EVENT_MAPPER.toNewSubscriptionEvent(subscribedUser));
}

@ExecutionTime
public void sendNewCommentEvent(Comment comment, Post post, UserBasicResponse basicActionUser) {
sendMessageInternal(
applicationProperties.messageTopic().newComment(),
basicActionUser,
EVENT_MAPPER.toNewCommentEvent(comment, post));
}

@ExecutionTime
public void sendNewPostFollowingEvent(Post post, UserBasicResponse basicActionUser) {
sendMessageInternal(
applicationProperties.messageTopic().newPostFollowing(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import module java.base;

import com.vulinh.annotation.ExecutionTime;
import com.vulinh.configuration.data.ApplicationProperties;
import com.vulinh.data.dto.response.KeycloakUserResponse;
import com.vulinh.data.mapper.KeycloakMapper;
Expand All @@ -22,6 +23,7 @@ public class KeycloakAdminClientService {

final Keycloak keycloak;

@ExecutionTime
@NonNull
public KeycloakUserResponse getKeycloakUser(UUID userId) {
try {
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/application-development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ logging.level:
PostEditValidationService: DEBUG
category.CategoryService: DEBUG
configuration.com.vulinh.configuration.SecurityConfiguration: DEBUG
org.springframework.security.oauth2: TRACE
# org.springframework.security.oauth2: TRACE
# Remove the comment sign for extra debugging information
1 change: 1 addition & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ application-properties:
no-authenticated-urls:
- /free/**
- /health
- /health/**
- /v3/api-docs/**
- /swagger-ui.html
- /swagger-ui/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ M9999=Internal Server error, please contact the development team!
#
# Authorization errors
#
M9102=Invalid authorization info
M9401=Invalid authentication info
M9403=Insufficient permission to access the resource
M9106=Invalid request body (%s)
M9107=Invalid author or no permission to edit
#
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/i18n/messages_vi.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ M9999=\u0110\u00E3 c\u00F3 l\u1ED7i x\u1EA3y ra, vui l\u00F2ng li\u00EAn h\u1EC7
#
# Authorization errors
#
M9102=Th\u00F4ng tin x\u00E1c th\u1EF1c kh\u00F4ng h\u1EE3p l\u1EC7
M9401=Th\u00F4ng tin x\u00E1c th\u1EF1c kh\u00F4ng h\u1EE3p l\u1EC7
M9403=Kh\u00F4ng c\u00F3 quy\u1EC1n h\u1EA1n truy c\u1EADp t\u00E0i nguy\u00EAn
M9106=B\u1EA3n tin kh\u00F4ng h\u1EE3p l\u1EC7 (%s)
M9107=Kh\u00F4ng ph\u1EA3i t\u00E1c gi\u1EA3 ho\u1EB7c kh\u00F4ng c\u00F3 quy\u1EC1n
#
Expand Down