我正在开发基于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
应该能够对基于`routeB
USER
加上POST
和DELETE
的相同操作
无论如何,我得到的行为是我可以向任何终结点发出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();
}
}
答案 0 :(得分:7)
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();
}
答案 1 :(得分:1)
跨站点请求伪造是一个网络安全漏洞,攻击者可以利用该漏洞诱使用户执行他们不执行的操作 打算表演。
就您而言,禁用 CSRF 保护会使用户暴露于此漏洞中。
注意:如果它是具有O-Auth保护的纯Rest API,则CSRF不是 需要。 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令牌。在会话期满之前,这不会改变。 另请阅读:Spring Security’s CSRF protection for REST services: the client side and the server side,以更好地理解。
答案 2 :(得分:0)
这是启用了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();
将解决问题。