Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3ae568e
Add authentication, security, and JWT functionality
NourAlPha Apr 27, 2025
86d51d2
Add authentication, security, and JWT functionality
NourAlPha Apr 27, 2025
c7a6772
Update src/main/java/com/podzilla/auth/model/User.java
NourAlPha Apr 27, 2025
96bcc51
fix test error
NourAlPha Apr 27, 2025
bf83761
Merge branch 'Add-auth' of https://github.com/Podzilla/authentication…
NourAlPha Apr 27, 2025
920aad0
Create tmp
NourAlPha Apr 27, 2025
aeb7cf1
Fix authApplication
NourAlPha Apr 27, 2025
71785a1
Add refresh token
NourAlPha Apr 27, 2025
c4f7560
Add refresh token to the cookie instead of returning it back to the f…
NourAlPha Apr 27, 2025
8f9f175
Add admin role get all users api
NourAlPha Apr 28, 2025
f8f9b21
Update src/main/java/com/podzilla/auth/controller/AuthenticationContr…
NourAlPha Apr 28, 2025
0ce8b9c
Update src/main/java/com/podzilla/auth/controller/AuthenticationContr…
NourAlPha Apr 28, 2025
837add8
Update .github/workflows/ci-cd.yml
NourAlPha Apr 28, 2025
7235218
Rename signup endpoint to register and enhance refresh token management
NourAlPha Apr 28, 2025
a702c83
Add Swagger annotations for API documentation and implement custom ex…
NourAlPha Apr 28, 2025
e86ada4
Add Swagger annotations for API documentation and implement custom ex…
NourAlPha Apr 28, 2025
cd51b0e
Configure SpringDoc OpenAPI settings and permit access to Swagger UI …
NourAlPha Apr 28, 2025
251dc5b
Refactor API endpoints and rename JWTService to TokenService
NourAlPha Apr 30, 2025
fa225f6
Set server context path to /api in application properties
NourAlPha Apr 30, 2025
deb3870
Add Redis caching support and update Docker configuration for Redis s…
NourAlPha Apr 30, 2025
5068d72
Update Docker images for PostgreSQL, Grafana, Loki, Promtail, Redis, …
NourAlPha Apr 30, 2025
af4eecb
Refactor API endpoints and rename JWTService to TokenService
NourAlPha Apr 30, 2025
f5300fb
Set server context path to /api in application properties
NourAlPha Apr 30, 2025
4d13b40
Add Redis caching support and update Docker configuration for Redis s…
NourAlPha Apr 30, 2025
20c7ad4
Update Docker images for PostgreSQL, Grafana, Loki, Promtail, Redis, …
NourAlPha Apr 30, 2025
15eb1ee
Merge branch 'add-cache' of https://github.com/Podzilla/authenticatio…
NourAlPha Apr 30, 2025
715f235
Enhance global exception handling and logging for authentication erro…
NourAlPha Apr 30, 2025
2796156
Refactor authentication and token management: use builder pattern for…
NourAlPha Apr 30, 2025
6853011
Update security configuration to permit access to refresh token endpoint
NourAlPha Apr 30, 2025
0d6d721
Implement custom user details and enhance authentication: add CustomU…
NourAlPha May 1, 2025
274eb06
Create tmp
NourAlPha Apr 27, 2025
8d93c55
Fix authApplication
NourAlPha Apr 27, 2025
9c942a0
Add refresh token
NourAlPha Apr 27, 2025
b177e81
Add refresh token to the cookie instead of returning it back to the f…
NourAlPha Apr 27, 2025
70e0cef
Rename signup endpoint to register and enhance refresh token management
NourAlPha Apr 28, 2025
35b27e7
Add Swagger annotations for API documentation and implement custom ex…
NourAlPha Apr 28, 2025
aa71af9
Refactor API endpoints and rename JWTService to TokenService
NourAlPha Apr 30, 2025
b4f9750
Add Redis caching support and update Docker configuration for Redis s…
NourAlPha Apr 30, 2025
4efc873
Update Docker images for PostgreSQL, Grafana, Loki, Promtail, Redis, …
NourAlPha Apr 30, 2025
2df2cb3
Enhance global exception handling and logging for authentication erro…
NourAlPha Apr 30, 2025
18b0a65
Refactor authentication and token management: use builder pattern for…
NourAlPha Apr 30, 2025
6ca3a55
Update security configuration to permit access to refresh token endpoint
NourAlPha Apr 30, 2025
78f2eb6
Implement custom user details and enhance authentication: add CustomU…
NourAlPha May 1, 2025
dbee902
Merge branch 'add-cache' of https://github.com/Podzilla/authenticatio…
NourAlPha May 1, 2025
8a08f1d
test
NourAlPha May 1, 2025
6a182ce
tst2
NourAlPha May 1, 2025
5158aa4
Remove debug logging from login method in AuthenticationService
NourAlPha May 1, 2025
a3baaf1
Add unit tests for AdminService, AuthenticationService, CustomUserDet…
NourAlPha May 1, 2025
7d9ab5d
Validate role existence before assigning to account in Authentication…
NourAlPha May 1, 2025
794a650
test
NourAlPha May 1, 2025
1381520
check linter
NourAlPha May 1, 2025
97b5335
try linter
NourAlPha May 1, 2025
5dd728a
Add unit tests for AdminController and update role authority handling
NourAlPha May 1, 2025
60458eb
Refactor authentication tests and improve role handling in AdminContr…
NourAlPha May 1, 2025
a784ec6
Add cache configuration and update application properties for cache m…
NourAlPha May 1, 2025
86e1c23
Enhance authentication flow and caching: add checks for logged-in use…
NourAlPha May 2, 2025
5b2b6e5
Refactor equals method in CustomUserDetails for improved readability
NourAlPha May 2, 2025
fd46358
Refactor account registration and token handling: streamline null che…
NourAlPha May 3, 2025
5b178e3
Refactor account registration and token handling: streamline null che…
NourAlPha May 3, 2025
7029f7e
Refactor CustomUserDetails constructor: improve parameter order and c…
NourAlPha May 3, 2025
f464838
Update validation message in AuthenticationServiceTest and enable use…
NourAlPha May 3, 2025
2e202e0
Set user as enabled during account creation in AuthenticationService
NourAlPha May 3, 2025
79518c9
Remove automatic user enablement during account creation in Authentic…
NourAlPha May 3, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
logs/
*.log
secret.env

### STS ###
.apt_generated
Expand Down
28 changes: 24 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
services:
backend:
image: openjdk:25-ea-4-jdk-oraclelinux9
container_name: auth
ports:
- "8080:8080"
env_file:
Expand All @@ -9,13 +10,15 @@ services:
- auth_db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://auth_db:5432/authDB
- SPRING_DATA_REDIS_HOST=redis_cache
volumes:
- ./target:/app
- ./logs:/logs
command: [ "java", "-jar", "/app/auth-0.0.1-SNAPSHOT.jar" ]

auth_db:
image: postgres:latest
image: postgres:14.17
container_name: auth_db
environment:
POSTGRES_PASSWORD: 1234
POSTGRES_USER: postgres
Expand All @@ -24,13 +27,15 @@ services:
- "5432:5432"

loki:
image: grafana/loki:latest
image: grafana/loki:3.5.0
container_name: loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml

promtail:
image: grafana/promtail:latest
image: grafana/promtail:3.5.0
container_name: promtail
volumes:
- ./promtail-config.yml:/etc/promtail/promtail-config.yaml
- ./logs:/logs
Expand All @@ -39,8 +44,23 @@ services:
- loki

grafana:
image: grafana/grafana:latest
image: grafana/grafana:11.6.1
container_name: grafana
ports:
- "3000:3000"
depends_on:
- loki


redis_cache:
image: redis:7.4.3
container_name: redisCache
ports:
- "6379:6379"

redisinsight:
image: redis/redisinsight:2.68
container_name: redisInsight
ports:
- "5540:5540"
restart: always
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>


<!-- json web token -->
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/podzilla/auth/AuthApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class AuthApplication {

public static void main(final String[] args) {
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/podzilla/auth/dto/CustomGrantedAuthority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.podzilla.auth.dto;

import org.springframework.security.core.GrantedAuthority;

public class CustomGrantedAuthority implements GrantedAuthority {
private String authority;

public CustomGrantedAuthority() {
// No-arg constructor required by Jackson
}

public CustomGrantedAuthority(final String authority) {
this.authority = authority;
}

@Override
public String getAuthority() {
return authority;
}

@Override
public String toString() {
return "CustomGrantedAuthority{"
+ "authority='" + authority + '\''
+ '}';
}
}
110 changes: 110 additions & 0 deletions src/main/java/com/podzilla/auth/dto/CustomUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.podzilla.auth.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

@JsonIgnoreProperties(ignoreUnknown = true)
public class CustomUserDetails implements UserDetails {

private String username;

@JsonIgnore
private String password;

@JsonDeserialize(contentAs = CustomGrantedAuthority.class)
private Set<GrantedAuthority> authorities;

@Getter
private final boolean accountNonExpired;
@Getter
private final boolean accountNonLocked;
@Getter
private final boolean credentialsNonExpired;
@Getter
private final boolean enabled;

public CustomUserDetails() {
// No-arg constructor required by Jackson
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}

public CustomUserDetails(final String username, final String password,
final Set<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}

public CustomUserDetails(final String username,
final String password,
final boolean enabled,
final boolean accountNonExpired,
final boolean accountNonLocked,
final boolean credentialsNonExpired,
final Set<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
}

@Override
public String getUsername() {
return username;
}

@Override
public String getPassword() {
return password;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

public void eraseCredentials() {
this.password = null;
}

public int hashCode() {
return this.username.hashCode();
}

public boolean equals(final Object obj) {
if (obj instanceof CustomUserDetails) {
return this.username
.equals(((CustomUserDetails) obj).getUsername());
} else {
return false;
}
}

public String toString() {
return this.getClass().getName() + " ["
+ "Username=" + this.username + ", "
+ "Password=[PROTECTED], "
+ "Enabled=" + this.enabled + ", "
+ "AccountNonExpired=" + this.accountNonExpired + ", "
+ "CredentialsNonExpired=" + this.credentialsNonExpired + ", "
+ "AccountNonLocked=" + this.accountNonLocked + ", "
+ "Granted Authorities=" + this.authorities + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,31 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class GlobalExceptionHandler {
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {


@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDeniedException(
final AccessDeniedException exception) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.FORBIDDEN));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthenticationException(
final AuthenticationException exception) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.UNAUTHORIZED));
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFoundException(
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/podzilla/auth/model/RefreshToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

Expand All @@ -21,8 +23,10 @@
@Entity
@Table(name = "refresh_tokens")
@Getter
@Builder
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class RefreshToken {

Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/podzilla/auth/redis/RedisCacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.podzilla.auth.redis;

import com.podzilla.auth.dto.CustomUserDetails;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

@Configuration
public class RedisCacheConfig {

private static final int CACHE_TTL = 60 * 60;

@Bean
public CacheManager cacheManager(
final RedisConnectionFactory redisConnectionFactory,
@Value("${appconfig.cache.enabled}") final String cacheEnabled) {
if (!Boolean.parseBoolean(cacheEnabled)) {
return new NoOpCacheManager();
}

RedisCacheConfiguration defaultConfig = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(CACHE_TTL))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.
SerializationPair.
fromSerializer(
new Jackson2JsonRedisSerializer<>(
CustomUserDetails.class)));

return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultConfig)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ protected void doFilterInternal(final HttpServletRequest request,
String userEmail = tokenService.extractEmail();

UserDetails userDetails =
customUserDetailsService.loadUserByUsername(userEmail);
customUserDetailsService
.loadUserByUsernameCached(userEmail);

UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
Expand All @@ -62,7 +63,7 @@ protected void doFilterInternal(final HttpServletRequest request,
context.setAuthentication(authToken);
SecurityContextHolder.setContext(context);


LOGGER.info("User {} authenticated", userEmail);
} catch (Exception e) {
LOGGER.error("Invalid JWT token: {}", e.getMessage());
}
Expand Down

This file was deleted.

Loading