Skip to content

Commit fcb6d18

Browse files
committed
add jwt auth/authorization
1 parent 3ad8cb3 commit fcb6d18

32 files changed

+1434
-131
lines changed

README_TEST.md

Whitespace-only changes.

pom.xml

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<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">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
34
<modelVersion>4.0.0</modelVersion>
45

56
<groupId>com.apina.api</groupId>
@@ -41,6 +42,28 @@
4142
</properties>
4243

4344
<dependencies>
45+
<dependency>
46+
<groupId>io.jsonwebtoken</groupId>
47+
<artifactId>jjwt-api</artifactId>
48+
<version>0.11.5</version>
49+
</dependency>
50+
<dependency>
51+
<groupId>io.jsonwebtoken</groupId>
52+
<artifactId>jjwt-impl</artifactId>
53+
<version>0.11.5</version>
54+
<scope>runtime</scope>
55+
</dependency>
56+
<dependency>
57+
<groupId>io.jsonwebtoken</groupId>
58+
<artifactId>jjwt-jackson</artifactId>
59+
<version>0.11.5</version>
60+
<scope>runtime</scope>
61+
</dependency>
62+
63+
<dependency>
64+
<groupId>org.springframework.boot</groupId>
65+
<artifactId>spring-boot-starter-security</artifactId>
66+
</dependency>
4467
<dependency>
4568
<groupId>org.mongodb</groupId>
4669
<artifactId>mongodb-driver-sync</artifactId>
@@ -50,16 +73,22 @@
5073
<groupId>org.springframework.boot</groupId>
5174
<artifactId>spring-boot-starter-web</artifactId>
5275
</dependency>
53-
<dependency>
54-
<groupId>org.springframework.boot</groupId>
55-
<artifactId>spring-boot-starter-test</artifactId>
56-
<scope>test</scope>
57-
</dependency>
5876
<dependency>
5977
<groupId>org.springdoc</groupId>
6078
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
6179
<version>${springdoc-openapi-starter-webmvc-ui.version}</version>
6280
</dependency>
81+
<dependency>
82+
<groupId>org.jetbrains</groupId>
83+
<artifactId>annotations</artifactId>
84+
<version>24.1.0</version>
85+
<scope>compile</scope>
86+
</dependency>
87+
<dependency>
88+
<groupId>org.junit.jupiter</groupId>
89+
<artifactId>junit-jupiter</artifactId>
90+
<scope>test</scope>
91+
</dependency>
6392
</dependencies>
6493

6594
<build>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.apina.api.config;
2+
3+
import com.apina.api.models.UserEntity;
4+
import com.apina.api.repositories.UserRepository;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.security.authentication.AuthenticationManager;
8+
import org.springframework.security.authentication.AuthenticationProvider;
9+
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
10+
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
11+
import org.springframework.security.core.GrantedAuthority;
12+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
13+
import org.springframework.security.core.userdetails.User;
14+
import org.springframework.security.core.userdetails.UserDetailsService;
15+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
16+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
17+
import org.springframework.security.crypto.password.PasswordEncoder;
18+
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
//ApplicationConfig.java
23+
@Configuration
24+
public class ApplicationConfig {
25+
26+
private final UserRepository userRepository;
27+
28+
public ApplicationConfig(UserRepository userRepository) {
29+
this.userRepository = userRepository;
30+
}
31+
@Bean
32+
public PasswordEncoder passwordEncoder() {
33+
return new BCryptPasswordEncoder();
34+
}
35+
36+
@Bean
37+
AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
38+
return authConfig.getAuthenticationManager();
39+
}
40+
41+
@Bean
42+
UserDetailsService userDetailsService() {
43+
return username -> {
44+
UserEntity user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));
45+
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
46+
user.getRole().forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
47+
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
48+
};
49+
}
50+
51+
@Bean
52+
AuthenticationProvider authenticationProvider() {
53+
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
54+
authProvider.setUserDetailsService(userDetailsService());
55+
authProvider.setPasswordEncoder(passwordEncoder());
56+
return authProvider;
57+
}
58+
59+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.apina.api.config;
2+
3+
import com.apina.api.services.JwtService;
4+
import jakarta.servlet.FilterChain;
5+
import jakarta.servlet.ServletException;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import org.springframework.context.annotation.Lazy;
11+
import org.springframework.lang.NonNull;
12+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13+
import org.springframework.security.core.Authentication;
14+
import org.springframework.security.core.context.SecurityContextHolder;
15+
import org.springframework.security.core.userdetails.UserDetails;
16+
import org.springframework.security.core.userdetails.UserDetailsService;
17+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
18+
import org.springframework.stereotype.Component;
19+
import org.springframework.web.filter.OncePerRequestFilter;
20+
import org.springframework.web.servlet.HandlerExceptionResolver;
21+
22+
import java.io.IOException;
23+
@Component
24+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
25+
26+
private final HandlerExceptionResolver handlerExceptionResolver;
27+
private final JwtService jwtService;
28+
private final UserDetailsService userDetailsService;
29+
30+
public JwtAuthenticationFilter(@Lazy JwtService jwtService, @Lazy UserDetailsService userDetailsService, @Lazy HandlerExceptionResolver handlerExceptionResolver) {
31+
this.handlerExceptionResolver = handlerExceptionResolver;
32+
this.jwtService = jwtService;
33+
this.userDetailsService = userDetailsService;
34+
}
35+
36+
@Override
37+
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
38+
final String authHeader = request.getHeader("Authorization");
39+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
40+
filterChain.doFilter(request, response);
41+
return;
42+
}
43+
try {
44+
final String jwt = authHeader.substring(7);
45+
final String userName = this.jwtService.extractUsername(jwt);
46+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
47+
if (userName != null && authentication == null) {
48+
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
49+
if (this.jwtService.isTokenValid(jwt, userDetails)) {
50+
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
51+
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
52+
SecurityContextHolder.getContext().setAuthentication(authToken);
53+
}
54+
}
55+
filterChain.doFilter(request, response);
56+
} catch (Exception exception) {
57+
handlerExceptionResolver.resolveException(request, response, null, exception);
58+
}
59+
}
60+
}
61+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.apina.api.config;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.http.HttpMethod;
7+
import org.springframework.security.authentication.AuthenticationProvider;
8+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
10+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
11+
import org.springframework.security.config.http.SessionCreationPolicy;
12+
import org.springframework.security.web.SecurityFilterChain;
13+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
15+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
16+
// SecurityConfig.java
17+
@Configuration
18+
@EnableWebSecurity
19+
public class SecurityConfig implements WebMvcConfigurer {
20+
21+
private final AuthenticationProvider authenticationProvider;
22+
private final JwtAuthenticationFilter jwtAuthenticationFilter;
23+
24+
private static final String ADMIN = "ADMIN";
25+
private static final String API_GYM = "/api/gym/**";
26+
private static final String API_GYMS = "/api/gyms/**";
27+
private static final String API_USER = "/api/user/**";
28+
29+
@Value("${app.cors.allowed-origins}")
30+
private String allowedOrigin;
31+
32+
public SecurityConfig(AuthenticationProvider authenticationProvider, JwtAuthenticationFilter jwtAuthenticationFilter) {
33+
this.authenticationProvider = authenticationProvider;
34+
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
35+
}
36+
37+
38+
@Bean
39+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
40+
http.csrf(AbstractHttpConfigurer::disable)
41+
.authorizeHttpRequests(auth -> auth
42+
.requestMatchers("/api/auth/**","/", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**").permitAll()
43+
.requestMatchers(HttpMethod.DELETE, API_GYMS, API_GYM, API_USER).hasRole(ADMIN)
44+
.requestMatchers(HttpMethod.POST, API_GYMS, API_GYM).hasRole(ADMIN)
45+
.requestMatchers(HttpMethod.PUT, API_GYMS, API_GYM, API_USER).hasRole(ADMIN)
46+
.requestMatchers(HttpMethod.GET, API_GYMS, API_GYM, API_USER).permitAll()
47+
.anyRequest()
48+
.authenticated())
49+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
50+
.authenticationProvider(authenticationProvider)
51+
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
52+
return http.build();
53+
}
54+
55+
@Override
56+
public void addCorsMappings(CorsRegistry registry) {
57+
registry.addMapping("/api/**")
58+
.allowedOriginPatterns(allowedOrigin)
59+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
60+
.allowedHeaders("*")
61+
.allowCredentials(true);
62+
}
63+
}
64+

src/main/java/com/apina/api/config/WebConfig.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.apina.api.controllers;
2+
3+
import com.apina.api.dtos.LoginUserDTO;
4+
import com.apina.api.dtos.UserDTO;
5+
import com.apina.api.models.LoginResponse;
6+
import com.apina.api.models.UserEntity;
7+
import com.apina.api.services.AuthenticationService;
8+
import com.apina.api.services.JwtService;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
import org.springframework.http.HttpStatus;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.bind.annotation.*;
14+
//AuthController.java
15+
@RestController
16+
@RequestMapping("/api/auth")
17+
public class AuthController {
18+
19+
private static final Logger LOGGER = LoggerFactory.getLogger(AuthController.class);
20+
private final JwtService jwtService;
21+
22+
private final AuthenticationService authenticationService;
23+
24+
public AuthController(JwtService jwtService, AuthenticationService authenticationService) {
25+
this.jwtService = jwtService;
26+
this.authenticationService = authenticationService;
27+
}
28+
29+
@PostMapping("register")
30+
@ResponseStatus(HttpStatus.CREATED)
31+
public ResponseEntity<LoginResponse> register(@RequestBody UserDTO userDto) {
32+
if (authenticationService.exists(userDto.getUsername())) {
33+
LOGGER.info("User already exists: {}", userDto.getUsername());
34+
return ResponseEntity.status(HttpStatus.CONFLICT).build();
35+
}
36+
37+
String jwtToken = jwtService.generateToken(authenticationService.register(userDto).toUserEntity());
38+
return ResponseEntity.ok(new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirationTime()));
39+
}
40+
41+
@PostMapping("login")
42+
@ResponseStatus(HttpStatus.OK)
43+
public ResponseEntity<LoginResponse> authenticate(@RequestBody LoginUserDTO loginUserDto) {
44+
UserEntity authenticatedUser = authenticationService.authenticate(loginUserDto).toUserEntity();
45+
String jwtToken = jwtService.generateToken(authenticatedUser);
46+
return ResponseEntity.ok(new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirationTime()));
47+
}
48+
49+
}

0 commit comments

Comments
 (0)