From 4293ddb1571154bec7acf70d4c0f7d203399c785 Mon Sep 17 00:00:00 2001
From: Andrew Lalis
Date: Mon, 30 Jan 2023 14:08:02 +0100
Subject: [PATCH] Added explicitly defined endpoint security.
---
.../gymboard_api/config/SecurityConfig.java | 18 +++++++++++-
...rityComponents.java => WebComponents.java} | 2 +-
.../controller/AuthController.java | 29 +++++++++++++++++--
.../service/auth/TokenService.java | 18 ++++++++++++
gymboard-app/src/pages/TestingPage.vue | 2 +-
5 files changed, 64 insertions(+), 5 deletions(-)
rename gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/{SecurityComponents.java => WebComponents.java} (92%)
diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java
index 6d016d8..2e79713 100644
--- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java
+++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java
@@ -3,6 +3,7 @@ package nl.andrewlalis.gymboard_api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -38,7 +39,22 @@ public class SecurityConfig {
.csrf().disable()
.cors().and()
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
- .authorizeHttpRequests().anyRequest().permitAll();
+ .authorizeHttpRequests()
+ .requestMatchers(// Allow the following GET endpoints to be public.
+ HttpMethod.GET,
+ "/exercises",
+ "/leaderboards",
+ "/gyms/**",
+ "/submissions/**"
+ ).permitAll()
+ .requestMatchers(// Allow the following POST endpoints to be public.
+ HttpMethod.POST,
+ "/gyms/submissions",
+ "/gyms/submissions/upload",
+ "/auth/token"
+ ).permitAll()
+ // Everything else must be authenticated, just to be safe.
+ .anyRequest().authenticated();
return http.build();
}
diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java
similarity index 92%
rename from gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java
rename to gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java
index 5af3e06..6ec6652 100644
--- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java
+++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java
@@ -6,7 +6,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
-public class SecurityComponents {
+public class WebComponents {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java
index 46b8e43..da03fe3 100644
--- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java
+++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java
@@ -5,7 +5,7 @@ import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse;
import nl.andrewlalis.gymboard_api.controller.dto.UserResponse;
import nl.andrewlalis.gymboard_api.model.auth.User;
import nl.andrewlalis.gymboard_api.service.auth.TokenService;
-import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -20,13 +20,38 @@ public class AuthController {
this.tokenService = tokenService;
}
+ /**
+ * Endpoint for obtaining a new access token for a user to access certain
+ * parts of the application. This is a public endpoint. If
+ * a token is successfully obtained, it should be provided to all subsequent
+ * requests as an Authorization "Bearer" token.
+ * @param credentials The credentials.
+ * @return The token the client should use.
+ */
@PostMapping(path = "/auth/token")
public TokenResponse getToken(@RequestBody TokenCredentials credentials) {
return tokenService.generateAccessToken(credentials);
}
+ /**
+ * Endpoint that can be used by an authenticated user to fetch a new access
+ * token using their current one; useful for staying logged in beyond the
+ * duration of the initial token's expiration.
+ * @param auth The current authentication.
+ * @return The new token the client should use.
+ */
+ @GetMapping(path = "/auth/token")
+ public TokenResponse getUpdatedToken(Authentication auth) {
+ return tokenService.regenerateAccessToken(auth);
+ }
+
+ /**
+ * Gets information about the user, as determined by the provided access
+ * token.
+ * @param user The user that requested information.
+ * @return The user data.
+ */
@GetMapping(path = "/auth/me")
- @PreAuthorize("isAuthenticated()")
public UserResponse getMyUser(@AuthenticationPrincipal User user) {
return new UserResponse(user);
}
diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java
index 038bc7d..6556d2b 100644
--- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java
+++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java
@@ -8,11 +8,13 @@ import nl.andrewlalis.gymboard_api.controller.dto.TokenCredentials;
import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse;
import nl.andrewlalis.gymboard_api.dao.auth.UserRepository;
import nl.andrewlalis.gymboard_api.model.auth.Role;
+import nl.andrewlalis.gymboard_api.model.auth.TokenAuthentication;
import nl.andrewlalis.gymboard_api.model.auth.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
@@ -67,6 +69,22 @@ public class TokenService {
if (!passwordEncoder.matches(credentials.password(), user.getPasswordHash())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
+ if (!user.isActivated()) {
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
+ }
+ String token = generateAccessToken(user);
+ return new TokenResponse(token);
+ }
+
+ public TokenResponse regenerateAccessToken(Authentication auth) {
+ if (!(auth instanceof TokenAuthentication tokenAuth)) {
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
+ }
+ User user = userRepository.findByIdWithRoles(tokenAuth.user().getId())
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ if (!user.isActivated()) {
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
+ }
String token = generateAccessToken(user);
return new TokenResponse(token);
}
diff --git a/gymboard-app/src/pages/TestingPage.vue b/gymboard-app/src/pages/TestingPage.vue
index b9eca7f..fe97c9b 100644
--- a/gymboard-app/src/pages/TestingPage.vue
+++ b/gymboard-app/src/pages/TestingPage.vue
@@ -5,7 +5,7 @@
Use this page to test new functionality, before adding it to the main
app. This page should be hidden on production.
-