Я разрабатываю REST API на основе Spring Boot (spring-boot-starter-web), где я использую Spring Security (spring-security-core e spring-security-config) для защиты различных конечных точек.
Аутентификация выполняется с использованием локальной базы данных, которая содержит пользователей с двумя разными наборами ролей: ADMIN и USER. USER должен иметь возможность подключать GET ко всем конечным точкам API, а POST - к конечным точкам на основе routeA. ADMIN должен иметь возможность делать то же самое, что и USER плюс POST и DELETE с конечными точками на основе `routeB
Однако поведение, которое я получаю, заключается в том, что я могу выполнять запросы GET к любой конечной точке, но запросы POST всегда возвращают HTTP 403 Forbidden для любого типа пользователя - ADMIN и USER - чего не ожидалось, исходя из моего SecurityConfiguration.
Любые идеи о том, что мне не хватает?
SecurityConfiguration.java
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
@Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private DataSource dataSource;
@Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
logger.info("Using database as the authentication provider.");
builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/*").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/*").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
RouteBController .java
@RestController
public class RouteBController {
static final Logger logger = LoggerFactory.getLogger(RouteBController.class);
public RouteBController() { }
@RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
public String getStuff() {
return "Got a hello world!";
}
@RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
public String postStuff() {
return "Posted a hello world!";
}
}
RESTAuthenticationEntryPoint.java
@Component
public class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("AppNameHere");
super.afterPropertiesSet();
}
}




ПЕРЕД отключение CSFR как способ решения этой проблемы, пожалуйста, проверьте ресурсы на Ответ Мохда Васима, чтобы лучше понять, почему это важно, и иметь представление о том, как его можно правильно настроить. Как RCaetano сказал, CSFR здесь, чтобы помочь нам от атак, и его не следует отключать вслепую.
Поскольку в этом ответе все еще объясняются 2 проблемы на мои исходные вопросы, я оставлю его как отмеченный ответ, чтобы информировать о возможных проблемах с CSFT и маршрутами безопасности, но не воспринимайте это буквально.
В SecurityConfiguration.java было 2 проблемы, из-за которых он работал некорректно.
Хотя сообщение об ошибке 403 Forbidden не содержало никаких сообщений, указывающих на причину сбоя (см. Пример ниже), оказывается, что это произошло из-за включения CSRF. Отключение позволило обрабатывать запросы POST и DELETE.
{
"timestamp": "2018-06-26T09:17:19.672+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/routeB"
}
Также выражение, используемое в antMatched(HttpMethod, String) для RouteB, было неверным, потому что /routeB/* ожидает, что он будет иметь что-нибудь после /. Правильная конфигурация - /routeB/**, поскольку присутствует больше путей может (или нет).
исправленоSecurityConfiguration.java - это
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/**").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors().and().
csrf().disable();
}
Source:StackOverflow em Português
Я знаю, что этому вопросу несколько месяцев / лет, но насколько я знаю, CSRF не следует отключать в производственной среде, не так ли?
Это решение не следует использовать вслепую, потому что есть причина, по которой CSRF включен по умолчанию: для защиты от определенных типов атак.
@Igor как было сказано в этих комментариях, CSRF не следует отключать вслепую. Вы можете настроить его в соответствии со своими потребностями, чтобы избежать возникшей у меня проблемы, сохранив при этом преимущества безопасности, которые он предлагает.
@RCaetano спасибо за ваш комментарий, я не видел, чтобы это комментировалось раньше. Я оставлю ответ, так как считаю, что он дает представление о возможных проблемах с CSFT и маршрутами безопасности, но я указал на лучшие решения / ресурсы, которыми поделились в этом посте.
Его простая проблема с включенным CSRF, которая не разрешает запросы POST. Я столкнулся с той же проблемой, вот решение: (Объяснил)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/form").hasRole("ADMIN") // Specific api method request based on role.
.antMatchers("/home","/basic").permitAll() // permited urls to guest users(without login).
.anyRequest().authenticated()
.and()
.formLogin() // not specified form page to use default login page of spring security
.permitAll()
.and()
.logout().deleteCookies("JSESSIONID") // delete memory of browser after logout
.and()
.rememberMe().key("uniqueAndSecret"); // remember me check box enabled.
http.csrf().disable(); **// ADD THIS CODE TO DISABLE CSRF IN PROJECT.**
}
Над кодом:
http.csrf().disable();
решит проблему.
Cross-site request forgery is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform.
В вашем случае отключение защиты CSRF подвергает пользователя этой уязвимости.
Note: If it was pure Rest API with O-Auth protection then CSRF was not needed. Should I use CSRF protection on Rest API endpoints?
Но в вашем случае, когда пользователь входит в систему, создается сеанс и в ответ возвращается cookie, и без токена CSRF Злоумышленник может использовать его и выполнить CSRF.
Было бы неплохо отключать CSRF, вместо этого вы можете настроить свое приложение на возврат токена CSRF в заголовках ответов, а затем использовать его во всех ваших последующих вызовах изменения состояния.
Добавьте эту строку кода в свой SecurityConfiguration.java
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
CsrfTokenResponseHeaderBindingFilter.java
public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
if (token != null) {
response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
}
filterChain.doFilter(request, response);
}
}
Сервер формы ответа заголовка:

Обратите внимание, что теперь у нас есть токен CSRF в заголовке. Это не изменится, пока не истечет сеанс. Также прочтите: CSRF-защита Spring Security для служб REST: на стороне клиента и на стороне сервера для лучшего понимания.
(Другое) Обязательно используйте правильный импорт: org.springframework.security.web.csrf.CsrfToken, или вы можете получить: class org.springframework.security.web.csrf.DefaultCsrfToken cannot be cast to class org.springframework.security.web.server.csrf.CsrfToken
CSRF ... уф! БЛАГОДАРНОСТЬ!