Skip to content

Instantly share code, notes, and snippets.

@realimp
Created December 31, 2023 12:16
Show Gist options
  • Save realimp/5e732214dee95430a61ccac20acf7f41 to your computer and use it in GitHub Desktop.
Save realimp/5e732214dee95430a61ccac20acf7f41 to your computer and use it in GitHub Desktop.
Spring Security Exceptions handling
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import pro.nikolaev.restutils.dto.ApiError;
import java.io.OutputStream;
import static ru.megafon.iot.tableexporter.constants.SecurityConstants.*;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final ObjectMapper objectMapper;
public SecurityConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public UserDetailsService userDetailsService(@Value("${app.api.users.user.name}") String user,
@Value("${app.api.users.user.password}") String userPassword,
@Value("${app.api.roles.user}") String userRole,
@Value("${app.api.users.admin.name}") String admin,
@Value("${app.api.users.admin.password}") String adminPassword,
@Value("${app.api.roles.admin}") String adminRole,
@Value("${app.api.users.monitoring.name}") String monitoring,
@Value("${app.api.users.monitoring.password}") String monitoringPassword,
@Value("${app.api.roles.monitoring}") String monitoringRole) {
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(
User.withUsername(user)
.password(userPassword)
.roles(userRole)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build());
userDetailsManager.createUser(
User.withUsername(admin)
.password(adminPassword)
.roles(adminRole)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build());
userDetailsManager.createUser(
User.withUsername(monitoring)
.password(monitoringPassword)
.roles(monitoringRole)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build());
return userDetailsManager;
}
@Bean
public SecurityFilterChain filterChain(final HttpSecurity httpSecurity,
@Value("${app.api-docs.enabled}") boolean apiDocs) throws Exception {
// Добавляем политики обработки запросов в соответствии с РМД
httpSecurity.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {
// Открываем доступ к API для авторизованных пользователей
authorizationManagerRequestMatcherRegistry
.requestMatchers("/api/v1/user/**").authenticated();
authorizationManagerRequestMatcherRegistry
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN");
// Открываем доступ к метрикам
authorizationManagerRequestMatcherRegistry
.requestMatchers(HttpMethod.GET, "/actuator/**").hasRole("MONITORING");
if (apiDocs) {
// Открываем доступ к Swagger-UI если он включен
authorizationManagerRequestMatcherRegistry
.requestMatchers(HttpMethod.GET, "/api-docs/**").permitAll();
}
});
// Добавляем обработку ошибок
httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
httpSecurityExceptionHandlingConfigurer
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setHeader(HttpHeaders.CONNECTION, "Close");
OutputStream responseStream = response.getOutputStream();
objectMapper.writeValue(responseStream, new ApiError(FORBIDDEN, INSUFFICIENT_PERMISSIONS));
responseStream.flush();
});
httpSecurityExceptionHandlingConfigurer
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setHeader(HttpHeaders.CONNECTION, "Close");
OutputStream responseStream = response.getOutputStream();
objectMapper.writeValue(responseStream, new ApiError(UNAUTHORIZED, BAD_CREDENTIALS));
responseStream.flush();
});
});
// Отключаем использование HttpSession
httpSecurity.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return httpSecurity.httpBasic(Customizer.withDefaults()).build();
}
@Bean
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
return (registry) -> registry.observationConfig().observationPredicate(predicate);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment