CSRF Token not accepted (Spring) - spring

I run into a problem when adding CSRF to my existing and working CORS configuration.
Everytime a POST, PUT or DELETE is triggered I get the error that the current token I have is not the right one (nvalid CSRF Token 'edff86dc-093a-4df9-8218-e5343506bdf9' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.).
But when I compare them it can't be caused by the tokens. Also if i trigger a GET after that (e.g. PUT) the token before is sent again and accepted.
So I assume there might be a problem with my security config but I don't see what I'm missing.
security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
.antMatchers("/logout", "/admin/**").authenticated();
http.csrf().ignoringAntMatchers("/guestbook/**");
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
token filter:
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);
System.out.println(token.getToken());
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);
}
}
and for instance the cors filter:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("Origin");
response.addHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Max-Age", "10");
response.addHeader("Access-Control-Expose-Headers", "X-CSRF-TOKEN");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
String headers = request.getHeader("Access-Control-Request-Headers");
if (!StringUtils.isEmpty(headers )) {
response.addHeader("Access-Control-Allow-Headers", headers );
}
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else{
chain.doFilter(request, response);
}
}
The problem not only occurs when I'm logged in. If i would not disable csrf on the guestbook path there would also be no POST possible.
I hope anybody can give me a hint.
Greetings

So I finally solved my problem. After a lot of seach and error I discovered the CsrfProtectionMatcher which can be used to enable CSRF on different paths.
Anyways this was very confusing to me, because I thought CSRF would be a always enabled on every request by default. So as soon as I applied the CsrfProtectionMatcher on my "/admin" path (allowing all possible methods, which is specified as null) it worked. requireCsrfProtectionMatcher on docs.spring.io , detailed article
Also it's now possible for me, to work with a more simple configuration but despite that my old one works too.
old one with CsrfProtectionMatcher :
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private RegexRequestMatcher requestMatcher =
new RegexRequestMatcher("/admin", null);
public boolean matches(HttpServletRequest request) {
if(requestMatcher.matches(request))
return true;
return false;
}
};
http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher);
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
.antMatchers("/login", "/**/**/**").permitAll()
.antMatchers("/logout", "/admin/**").authenticated();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
more simple configuration:
http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.antMatchers("/**/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
I have to admit I still don't know why CSRF has to be explicit enabled. If anyone has an answer to that please tell me.

Related

Spring Security check api key in query

I have a service with a few endpoints and for all endpoints, there is a token in the header and the auth mechanism works just fine.
Now I have a new endpoint and the api_key for this endpoint will be sent in the query instead of the header. Is there anyway I can configure my filter to achieve this ?
I created a new filter which can authenticate apikey in query
public class SimpleFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if (!request.getQueryString().equals("API_KEY=abcd")) {
// throw exception
}
chain.doFilter(req, res);
}
#Override
public void destroy() {}
#Override
public void init(FilterConfig arg) {}
This is my configuration
http
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/new-api-with-key-in-query")
With this config, there is no auth check for my new api, but my question is, how do I enable my new filter just for the one api with keys in query.

JWT Interceptor Springboot

I'd like to make people who hold the JWT can access all APIs but people can only access on EXCLUDE PATH now. what should I set up for that?
This is my WebConfig.
private static final String[] EXCLUDE_PATHS = {
"/api/user/**"
};
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(EXCLUDE_PATHS);
This is my interceptor.
public class JwtInterceptor implements HandlerInterceptor {
private static final String HEADER_AUTH = "Authorization";
private final JwtTokenProvider jwtTokenProvider;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String token = request.getHeader(HEADER_AUTH);
if(token !=null && jwtTokenProvider.validateToken(token)){
return true;
}else{
throw new UnauthorizedException();
}
this is my validateToken fn
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
this is my doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
This is my security Config.
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf()
.ignoringAntMatchers("/h2-console/**")
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/user/**").hasRole("USER")
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
}
Am I missing something? I add the Security Config.
You should use WebSecurity instead of interceptors.
Something like this for configuring which paths can be accessed and which cannot
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
This link should help you well.

Adding parameters to header does not work while implementing SpringBoot Security JWT for REST API

I'm trying to implement authentication and authorization using JWT token in SpringBoot REST API.
In my JWTAuthentication class
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,
FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = Jwts.builder().setSubject(((User) auth.getPrincipal()).getUsername())
.claim("roles", ((User) auth.getPrincipal()).getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes()).compact();
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
chain.doFilter(req, res);
System.out.println("Token:"+token);
}
When I test my code by sending by posting the following message to 127.0.0.1:8080/login URL, I see that authentication is successful.
{"username":"admin", "password":"admin"}
And then Spring calls my JWT Authorization class
#Override
protected void doFilterInternal(
HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(SecurityConstants.HEADER_STRING);
if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
if (header == null) {
System.out.println("header null");
} else if (!header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
System.out.println("token prefix missing in header");
}
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
It prints the message: "token prefix missing in header"
Although I add the TOKEN_PREFIX in the successfulAuthentication method, it can not find it in the header in doFilterInternal method.
By the way, my security config is like this:
#EnableWebSecurity(debug = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private UserDetailsService userDetailsService;
#Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I checked the SpringBoot books but could not find a book that describes the inner details of the security framework. Since I did not understand how the framework works, I could not solve the problems by just looking at the blogs. Is there a book that you can suggest describing the details of SpringBoot Security?
Thanks
You set your token after you successfully authenticated the user to the header of
the Http response:
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
The internal JWT filter (from what I understand in your question is called after yours), looks in the Http headers of the request
String header = req.getHeader(SecurityConstants.HEADER_STRING);
and there they are not present.
In general, the second filter should not be active after you authenticated a user and should just return the JWT token to the client. Any subsequent call of the client should then include the JWT token in the Authorization header using Bearer: YourJWTToken for calling e.g. protected APIs.

How to setup CORS on user login

I'm getting Not injecting HSTS header error but still have no idea after googling this message.
o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#30cc5ff
What I have done is below.
API request http://localhost:8083/api/v1/users/login
Web config
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.permitAll();
http.csrf().disable().addFilterBefore(corsFilter, AuthorizationFilter.class)
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated();
protected AuthenticationFilter getAuthenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/api/v1/users/login");
return filter;
}
CorsFilter
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
// without this header jquery.ajax calls returns 401 even after successful login and SSESSIONID being succesfully stored.
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Authorization, Origin, Content-Type, Version");
response.setHeader("Access-Control-Expose-Headers", "X-Requested-With, Authorization, Origin, Content-Type");
final HttpServletRequest request = (HttpServletRequest) servletRequest;
if (!request.getMethod().equals("OPTIONS")) {
filterChain.doFilter(request, response);
} else {
// do not continue with filter chain for options requests
}
}
#Override
public void destroy() {
}
}
HSTS stands for Http Strict Transport Security and is one of the default headers being included when using Spring Security.
If you have your own security configuration set up and are sure you can disable the HSTS security header, use:
http.headers().httpStrictTransportSecurity().disable();

Authentication failure redirect with request params not working

I am trying to configure my own success and authentication failure handlers. On authentication failure I want to redirect back to my login page with a request parameter, the presence of this parameter will output the error message on my login page. However although on error I am getting redirected back to my login page, the request parameter is always null.
Code below:
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.successHandler(successHandler())
.failureHandler(handleAuthenticationFailure());
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//database checks
}
};
}
/**
* Authentication success handler defines action when successfully authenticated
* #return
*/
#Bean
public AuthenticationSuccessHandler successHandler(){
return new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Authentication authentication)
throws IOException, ServletException {
// custom auth success here
httpResponse.setStatus(HttpServletResponse.SC_OK);
SavedRequest savedRequest = (SavedRequest) httpRequest.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
httpResponse.sendRedirect(savedRequest.getRedirectUrl());
}
};
}
#Bean
public AuthenticationFailureHandler handleAuthenticationFailure() {
return new SimpleUrlAuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
AuthenticationException authenticationException) throws IOException, ServletException {
// custom failure code here
setDefaultFailureUrl("/login.html?error=fail");
super.onAuthenticationFailure(httpRequest, httpResponse, authenticationException);
}
};
}
Try with this:
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// .......
response.sendRedirect("/login.html?error=fail");
}
Update:
It's really important that the "/login.html?error=fail" is added to an authorizeRequests() section otherwise the controller won't pick up the error parameter.
Replace .antMatchers("/login").permitAll() with .antMatchers("/login**").permitAll()
Also had problem with params (in my case when login was failed and some request params was added to url it redirected to login page without params).
This solved my problem
.antMatchers("/login**").permitAll()
I'm new in springBoot, if you are using spring boot 2.1.4.RELEASE, try this configuration:
http.csrf().disable()
.authorizeRequests()
// URLs matching for access rights
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
// form login
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true")
.successHandler(sucessHandler)
.usernameParameter("email")
.passwordParameter("password")
.and()
// logout
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and()
.exceptionHandling()
.accessDeniedPage("/access-denied");
To use the above-defined Spring Security configuration, we need to attach it to the web application. In this case, we don’t need any web.xml:
public class SpringApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SecSecurityConfig.class};
}}
this means you create the following class which will be instanciated autoatically
SecSecurityConfig.class : is the class where you do all http.csrf().disable().authorizeRequests()... configurations
source : https://www.baeldung.com/spring-security-login
hope it helps :)

Resources