Compare commits
4 Commits
4e75e25d62
...
3641dbcdf9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3641dbcdf9 | ||
![]() |
656e0c0e7a | ||
![]() |
2e0c93a400 | ||
![]() |
ba239764bf |
@ -1,5 +1,6 @@
|
|||||||
package de.w665.biblenotes.config;
|
package de.w665.biblenotes.config;
|
||||||
|
|
||||||
|
import de.w665.biblenotes.rest.security.JwtAuthenticationFilter;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
@ -32,18 +33,17 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
// TODO: Fix security config for this project (currently old state from sharepulse)
|
|
||||||
|
|
||||||
http
|
http
|
||||||
.csrf(csrf -> csrf.ignoringRequestMatchers("/api/v1/**")) // Disable CSRF for API routes
|
.csrf(csrf -> csrf.ignoringRequestMatchers("/api/v1/**")) // Disable CSRF for API routes
|
||||||
.sessionManagement(sessionManagement -> sessionManagement
|
.sessionManagement(sessionManagement -> sessionManagement
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No session will be created by Spring Security
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No session will be created by Spring Security
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(authorize -> authorize
|
.authorizeHttpRequests(authorize -> authorize
|
||||||
.requestMatchers("/api/v1/secure/**").authenticated() // Secure these endpoints
|
.requestMatchers("/api/v1/auth/login").permitAll() // Allow access to login endpoint
|
||||||
|
.requestMatchers("/api/v1/**").authenticated() // Secure all other /api/v1/** routes
|
||||||
.anyRequest().permitAll() // All other requests are allowed without authentication
|
.anyRequest().permitAll() // All other requests are allowed without authentication
|
||||||
)
|
)
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Apply JWT filter
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Ensure JWT filter is applied after login is allowed
|
||||||
.logout(LogoutConfigurer::permitAll)
|
.logout(LogoutConfigurer::permitAll)
|
||||||
.rememberMe(Customizer.withDefaults());
|
.rememberMe(Customizer.withDefaults());
|
||||||
|
|
||||||
|
@ -54,45 +54,6 @@ public class RethinkDBService {
|
|||||||
log.debug("Database " + config.getDatabase() + " already exists. Error: " + e.getClass().getSimpleName());
|
log.debug("Database " + config.getDatabase() + " already exists. Error: " + e.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// rethinkdb check if table file_uploads exists
|
|
||||||
try {
|
|
||||||
r.db(config.getDatabase()).tableCreate("file_uploads").run(connection).stream();
|
|
||||||
log.debug("Table 'file_uploads' created successfully.");
|
|
||||||
} catch (ReqlOpFailedError e) {
|
|
||||||
log.debug("Table 'file_uploads' already exists.");
|
|
||||||
if(autoResetOnStartup) {
|
|
||||||
log.debug("Clearing content...");
|
|
||||||
r.db(config.getDatabase()).table("file_uploads").delete().run(connection);
|
|
||||||
log.debug("Table 'file_uploads' cleared successfully.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rethinkdb check if table id_store exists
|
|
||||||
try {
|
|
||||||
r.db(config.getDatabase()).tableCreate("id_store").run(connection).stream();
|
|
||||||
log.debug("Table 'id_store' created successfully.");
|
|
||||||
} catch (ReqlOpFailedError e) {
|
|
||||||
log.debug("Table 'id_store' already exists.");
|
|
||||||
if(autoResetOnStartup) {
|
|
||||||
log.debug("Clearing content...");
|
|
||||||
r.db(config.getDatabase()).table("id_store").delete().run(connection);
|
|
||||||
log.debug("Table 'id_store' cleared successfully.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rethinkdb check if table expired_file_uploads exists
|
|
||||||
try {
|
|
||||||
r.db(config.getDatabase()).tableCreate("expired_file_uploads").run(connection).stream();
|
|
||||||
log.debug("Table 'expired_file_uploads' created successfully.");
|
|
||||||
} catch (ReqlOpFailedError e) {
|
|
||||||
log.debug("Table 'expired_file_uploads' already exists.");
|
|
||||||
if(autoResetOnStartup) {
|
|
||||||
log.debug("Clearing content...");
|
|
||||||
r.db(config.getDatabase()).table("expired_file_uploads").delete().run(connection);
|
|
||||||
log.debug("Table 'expired_file_uploads' cleared successfully.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rethinkdb check if table users exists
|
// rethinkdb check if table users exists
|
||||||
try {
|
try {
|
||||||
r.db(config.getDatabase()).tableCreate("users").run(connection).stream();
|
r.db(config.getDatabase()).tableCreate("users").run(connection).stream();
|
||||||
|
10
src/main/java/de/w665/biblenotes/rest/ApiRestController.java
Normal file
10
src/main/java/de/w665/biblenotes/rest/ApiRestController.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package de.w665.biblenotes.rest;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1")
|
||||||
|
public abstract class ApiRestController {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package de.w665.biblenotes.rest.mappings;
|
||||||
|
|
||||||
|
import de.w665.biblenotes.rest.ro.AuthenticationRequest;
|
||||||
|
import de.w665.biblenotes.service.AuthenticationService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/auth")
|
||||||
|
public class AuthenticationController {
|
||||||
|
|
||||||
|
private final AuthenticationService authenticationService;
|
||||||
|
|
||||||
|
public AuthenticationController(AuthenticationService authenticationService) {
|
||||||
|
this.authenticationService = authenticationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<Object> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest, HttpServletRequest request) {
|
||||||
|
log.debug("Received AuthenticationRequest for username: " + authenticationRequest.getUsername());
|
||||||
|
String token = authenticationService.authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword(), request.getRemoteAddr());
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("token", token);
|
||||||
|
response.put("success", token != null);
|
||||||
|
|
||||||
|
if(token == null) {
|
||||||
|
log.debug("Authentication failed for username: " + authenticationRequest.getUsername());
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package de.w665.biblenotes.rest.mappings;
|
||||||
|
|
||||||
|
import de.w665.biblenotes.rest.ApiRestController;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class TestController extends ApiRestController {
|
||||||
|
|
||||||
|
@GetMapping("/test")
|
||||||
|
public String test() {
|
||||||
|
return "Test";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package de.w665.biblenotes.rest.ro;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class AuthenticationRequest {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
|||||||
package de.w665.biblenotes.rest.security;
|
package de.w665.biblenotes.rest.security;
|
||||||
|
|
||||||
|
import de.w665.biblenotes.service.AuthenticationService;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
@ -26,13 +28,19 @@ import java.util.List;
|
|||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final AuthenticationService authenticationService;
|
private final AuthenticationService authenticationService;
|
||||||
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/api/v1/secure/**");
|
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/api/v1/**"); // The filter will verify authentication for all requests starting with /api/v1/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
logger.debug("Filtering request: " + request.getRequestURI());
|
logger.debug("Filtering request: " + request.getRequestURI());
|
||||||
|
|
||||||
|
if ("/api/v1/auth/login".equals(request.getRequestURI())) {
|
||||||
|
logger.debug("Login request detected. Skipping JWT authentication.");
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!requestMatcher.matches(request)) {
|
if(!requestMatcher.matches(request)) {
|
||||||
logger.debug("Request does not match the secure path. Skipping JWT authentication.");
|
logger.debug("Request does not match the secure path. Skipping JWT authentication.");
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
package de.w665.biblenotes.service;
|
package de.w665.biblenotes.service;
|
||||||
|
|
||||||
|
import de.w665.biblenotes.db.repo.UserLoginRepository;
|
||||||
|
import de.w665.biblenotes.db.repo.UserRepository;
|
||||||
|
import de.w665.biblenotes.model.User;
|
||||||
|
import de.w665.biblenotes.model.UserLogin;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwt;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@ -17,9 +25,9 @@ public class AuthenticationService {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final UserLoginRepository userLoginRepository;
|
private final UserLoginRepository userLoginRepository;
|
||||||
|
|
||||||
@Value("${secureapi.jwt.secret}")
|
@Value("${jwt.secret}")
|
||||||
private String secretString;
|
private String secretString;
|
||||||
@Value("${secureapi.jwt.expiration}")
|
@Value("${jwt.expiration}")
|
||||||
private long expirationTime; // in milliseconds
|
private long expirationTime; // in milliseconds
|
||||||
private SecretKey secretKey;
|
private SecretKey secretKey;
|
||||||
|
|
||||||
@ -58,7 +66,7 @@ public class AuthenticationService {
|
|||||||
Date expiryDate = new Date(nowMillis + expirationTime);
|
Date expiryDate = new Date(nowMillis + expirationTime);
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject("SharePulse Authentication Token")
|
.subject("Biblenotes Authentication Token")
|
||||||
.issuedAt(now)
|
.issuedAt(now)
|
||||||
.claim("role", username.getRole())
|
.claim("role", username.getRole())
|
||||||
.claim("username", username.getUsername())
|
.claim("username", username.getUsername())
|
||||||
|
@ -1 +1,19 @@
|
|||||||
|
biblenotes.auto-reset-on-startup=false
|
||||||
|
biblenotes.management.user.username=admin
|
||||||
|
biblenotes.management.user.password=admin
|
||||||
|
|
||||||
|
# Database
|
||||||
|
rethinkdb.host=localhost
|
||||||
|
rethinkdb.port=28015
|
||||||
|
rethinkdb.database=biblenotes
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logging.level.de.w665.biblenotes=DEBUG
|
||||||
|
|
||||||
|
# Static path
|
||||||
|
spring.web.resources.static-locations=classpath:/static/browser/
|
||||||
|
|
||||||
|
server.port=80
|
||||||
spring.application.name=biblenotes
|
spring.application.name=biblenotes
|
||||||
|
jwt.secret=sampleKeyToChangeInProduction
|
||||||
|
jwt.expiration=3600000
|
Loading…
x
Reference in New Issue
Block a user