Created
December 31, 2023 12:16
-
-
Save realimp/5e732214dee95430a61ccac20acf7f41 to your computer and use it in GitHub Desktop.
Spring Security Exceptions handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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