profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
SecurityConfiguration.java 4,680 bytes
1 package com.tasteprint.account;
2
3 import java.io.IOException;
4 import java.net.URI;
5 import java.util.Arrays;
6 import java.util.List;
7
8 import com.fasterxml.jackson.databind.ObjectMapper;
9 import jakarta.servlet.DispatcherType;
10 import jakarta.servlet.http.HttpServletResponse;
11
12 import org.springframework.beans.factory.annotation.Value;
13 import org.springframework.context.annotation.Bean;
14 import org.springframework.context.annotation.Configuration;
15 import org.springframework.http.HttpMethod;
16 import org.springframework.http.HttpStatus;
17 import org.springframework.http.MediaType;
18 import org.springframework.http.ProblemDetail;
19 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
20 import org.springframework.security.config.http.SessionCreationPolicy;
21 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
22 import org.springframework.security.crypto.password.PasswordEncoder;
23 import org.springframework.security.web.SecurityFilterChain;
24 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
25 import org.springframework.web.cors.CorsConfiguration;
26 import org.springframework.web.cors.CorsConfigurationSource;
27 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
28
29 @Configuration
30 class SecurityConfiguration {
31
32 @Bean
33 PasswordEncoder passwordEncoder() {
34 return new BCryptPasswordEncoder(12);
35 }
36
37 @Bean
38 SecurityFilterChain securityFilterChain(HttpSecurity http, TokenAuthenticationFilter tokenFilter,
39 ObjectMapper objectMapper) throws Exception {
40 return http
41 .csrf(csrf -> csrf.disable())
42 .cors(cors -> { })
43 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
44 .authorizeHttpRequests(auth -> auth
45 .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
46 .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
47 .requestMatchers(HttpMethod.POST, "/api/v1/auth/register", "/api/v1/auth/login").permitAll()
48 .requestMatchers(HttpMethod.GET, "/api/v1/catalog/**", "/api/v1/public/**", "/uploads/**").permitAll()
49 .requestMatchers("/actuator/health/**", "/v3/api-docs/**", "/docs", "/docs/**", "/swagger-ui/**").permitAll()
50 .anyRequest().authenticated())
51 .exceptionHandling(errors -> errors
52 .authenticationEntryPoint((request, response, exception) ->
53 writeProblem(response, objectMapper, HttpStatus.UNAUTHORIZED,
54 "Authentication required", "Sign in to continue."))
55 .accessDeniedHandler((request, response, exception) ->
56 writeProblem(response, objectMapper, HttpStatus.FORBIDDEN,
57 "Forbidden", "You do not have access to this resource.")))
58 .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class)
59 .build();
60 }
61
62 @Bean
63 CorsConfigurationSource corsConfigurationSource(
64 @Value("${app.allowed-origins}") String allowedOrigins) {
65 CorsConfiguration configuration = new CorsConfiguration();
66 configuration.setAllowedOrigins(Arrays.stream(allowedOrigins.split(","))
67 .map(String::trim)
68 .filter(origin -> !origin.isBlank())
69 .toList());
70 configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
71 configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept"));
72 configuration.setExposedHeaders(List.of("Location"));
73 configuration.setAllowCredentials(true);
74 configuration.setMaxAge(3600L);
75 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
76 source.registerCorsConfiguration("/**", configuration);
77 return source;
78 }
79
80 private void writeProblem(HttpServletResponse response, ObjectMapper mapper, HttpStatus status,
81 String title, String detail) throws IOException {
82 ProblemDetail problem = ProblemDetail.forStatusAndDetail(status, detail);
83 problem.setTitle(title);
84 problem.setType(URI.create("https://tasteprint.app/problems/" + status.value()));
85 response.setStatus(status.value());
86 response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
87 mapper.writeValue(response.getOutputStream(), problem);
88 }
89 }
90