我正在开发一个基于 Spring Boot (
spring-boot-starter-web
) 的 REST API,其中我使用 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
的期待。
我想念什么有什么想法吗?
安全配置.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 作为解决此问题的方法之前,请检查Mohd Waseem 的答案上的资源,以更好地理解它为何如此重要,并了解如何正确设置它。正如 RCaetano 所说,CSFR 是为了帮助我们免受攻击,不应该盲目禁用它。
由于这个答案仍然解释了我原来问题的两个问题,所以我将其保留为标记答案,以提高人们对 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/*
期望在
/
之后有something。正确的配置是
/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();
}
在您的情况下,禁用跨站请求伪造是一种网络安全漏洞,允许攻击者诱导用户执行他们不执行的操作 打算表演。
CSRF 保护会使用户暴露于此漏洞。
注意:如果是带有 O-Auth 保护的纯 Rest API,则 CSRF 不是 需要。但是在您的情况下,当用户登录创建会话并返回 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 令牌。在会话到期之前,这不会改变。
另请阅读:Spring Security 针对 REST 服务的 CSRF 保护:客户端和服务器端,以便更好地理解。
解释)
@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();
就能解决问题。
在 http.build();之前
csrf().disable()
-> 无法工作,因为 csrf 已贬值
http.csrf(AbstractHttpConfigurer::disable);
#添加这一行对我有用返回http.build();