Add security on endpoints and handle 403 responses.
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
package org.sportshub.application.configuration;
|
package org.sportshub.application.configuration;
|
||||||
|
|
||||||
|
import static org.sportshub.domain.user.model.UserRole.ADMIN;
|
||||||
|
import static org.springframework.http.HttpMethod.GET;
|
||||||
|
import static org.springframework.http.HttpMethod.OPTIONS;
|
||||||
|
import static org.springframework.http.HttpMethod.POST;
|
||||||
|
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
||||||
import org.sportshub.application.security.JwtAuthenticationFilter;
|
import org.sportshub.application.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.http.HttpMethod;
|
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.configuration.EnableWebSecurity;
|
||||||
@@ -12,7 +16,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
|
|
||||||
import static jakarta.servlet.DispatcherType.FORWARD;
|
import static jakarta.servlet.DispatcherType.FORWARD;
|
||||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
@@ -29,27 +32,29 @@ public class SecurityConfiguration {
|
|||||||
httpSecurity
|
httpSecurity
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.httpBasic(Customizer.withDefaults())
|
.httpBasic(Customizer.withDefaults())
|
||||||
|
.exceptionHandling(configurer -> configurer
|
||||||
|
.authenticationEntryPoint((request, response, authException) -> response.sendError(SC_UNAUTHORIZED))
|
||||||
|
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(SC_FORBIDDEN))
|
||||||
|
)
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.sessionManagement(customizer -> customizer.sessionCreationPolicy(STATELESS))
|
||||||
.authorizeHttpRequests(requests -> requests
|
.authorizeHttpRequests(requests -> requests
|
||||||
.dispatcherTypeMatchers(FORWARD).permitAll()
|
.dispatcherTypeMatchers(FORWARD).permitAll()
|
||||||
.requestMatchers(
|
.requestMatchers(
|
||||||
HttpMethod.GET,
|
GET,
|
||||||
"/api/health/check"
|
"/api/health/check",
|
||||||
|
"/error"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.requestMatchers(
|
.requestMatchers(
|
||||||
HttpMethod.POST,
|
GET,
|
||||||
|
"/api/users"
|
||||||
|
).hasAuthority(ADMIN.name())
|
||||||
|
.requestMatchers(
|
||||||
|
POST,
|
||||||
"/api/users/login"
|
"/api/users/login"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
|
.requestMatchers(OPTIONS).permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
|
||||||
.exceptionHandling(configurer -> configurer
|
|
||||||
.defaultAuthenticationEntryPointFor(
|
|
||||||
(request, response, authException) -> response.sendError(SC_UNAUTHORIZED),
|
|
||||||
new AntPathRequestMatcher("/api/**")
|
|
||||||
).defaultAccessDeniedHandlerFor(
|
|
||||||
(request, response, accessDeniedException) -> response.sendError(SC_FORBIDDEN),
|
|
||||||
new AntPathRequestMatcher("/api/**")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return httpSecurity.build();
|
return httpSecurity.build();
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package org.sportshub.application.security.model;
|
package org.sportshub.application.security.model;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.sportshub.domain.user.model.User;
|
import org.sportshub.domain.user.model.User;
|
||||||
|
import org.sportshub.domain.user.model.UserRole;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
public class CustomUserDetails implements UserDetails {
|
public class CustomUserDetails implements UserDetails {
|
||||||
@@ -16,7 +17,12 @@ public class CustomUserDetails implements UserDetails {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return emptyList();
|
return user.roles()
|
||||||
|
.stream()
|
||||||
|
.map(UserRole::name)
|
||||||
|
.map(role -> "ROLE_" + role)
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.sportshub.domain.user.model;
|
package org.sportshub.domain.user.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public record User(
|
public record User(
|
||||||
UUID id,
|
UUID id,
|
||||||
String password
|
String password,
|
||||||
|
List<UserRole> roles
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.sportshub.domain.user.model;
|
||||||
|
|
||||||
|
public enum UserRole {
|
||||||
|
STANDARD,
|
||||||
|
ADMIN
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
package org.sportshub.exposition.user;
|
package org.sportshub.exposition.user;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.sportshub.application.user.UserUseCases;
|
import org.sportshub.application.user.UserUseCases;
|
||||||
import org.sportshub.domain.user.model.User;
|
import org.sportshub.domain.user.model.User;
|
||||||
import org.sportshub.exposition.user.model.LoginRequest;
|
import org.sportshub.exposition.user.model.LoginRequest;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.sportshub.domain.user.model.UserRole.ADMIN;
|
||||||
|
import static org.sportshub.domain.user.model.UserRole.STANDARD;
|
||||||
import org.sportshub.domain.user.model.User;
|
import org.sportshub.domain.user.model.User;
|
||||||
import org.sportshub.domain.user.port.UserPort;
|
import org.sportshub.domain.user.port.UserPort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -11,9 +13,21 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class UserInMemoryAdapter implements UserPort {
|
public class UserInMemoryAdapter implements UserPort {
|
||||||
private static final List<User> users = List.of(
|
private static final List<User> users = List.of(
|
||||||
new User(UUID.fromString("c1a0805f-c618-47dc-bae7-bee70503644e"), "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2"),
|
new User(
|
||||||
new User(UUID.fromString("4eff194d-dd8e-463e-974f-034bfd509f84"), "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2"),
|
UUID.fromString("c1a0805f-c618-47dc-bae7-bee70503644e"),
|
||||||
new User(UUID.fromString("c78d7d7c-0386-415d-86dc-98a470591e07"), "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2")
|
"$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2",
|
||||||
|
List.of(STANDARD)
|
||||||
|
),
|
||||||
|
new User(
|
||||||
|
UUID.fromString("4eff194d-dd8e-463e-974f-034bfd509f84"),
|
||||||
|
"$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2",
|
||||||
|
List.of(STANDARD)
|
||||||
|
),
|
||||||
|
new User(
|
||||||
|
UUID.fromString("c78d7d7c-0386-415d-86dc-98a470591e07"),
|
||||||
|
"$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2",
|
||||||
|
List.of(STANDARD, ADMIN)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ application:
|
|||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
org.springframework.security: DEBUG
|
org.springframework.security: DEBUG
|
||||||
|
|
||||||
|
server:
|
||||||
|
error:
|
||||||
|
whitelabel:
|
||||||
|
enabled: false # Disable html error responses.
|
||||||
|
include-stacktrace: never
|
||||||
Reference in New Issue
Block a user