CSRF token expires during login

前端 未结 4 2463
挽巷
挽巷 2021-02-01 08:57

I\'m working on Spring web application and I need to avoid problem with expire csrf token on login page, because if user is waiting too long and try to login only one way to res

相关标签:
4条回答
  • 2021-02-01 09:21

    Another option would be set no timeout for the session by default and then, when the user is authenticated, change the timeout to whatever you want. You can see an example of how to do this here.

    0 讨论(0)
  • 2021-02-01 09:22

    You can also make your CSRF protection rely on cookies and NOT server side session state. Spring Security has full support for this.

    CookieCsrfTokenRepository

    You will only receive a timeout if your cookie expires. This scales well since it's basically stateless (from the server's perspective).

    @EnableWebSecurity
    public class WebSecurityConfig extends
            WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf()
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        }
    }
    

    Andrew

    0 讨论(0)
  • 2021-02-01 09:23

    In one of the projects I worked on, I implemented the following:

    1. Implement an exception handler which handles CsrfException (or AccessDeniedException in general in my case). Forward the request to a controller method.

      @ExceptionHandler(AccessDeniedException.class)
      @ResponseStatus(value = HttpStatus.FORBIDDEN)
      public void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
          request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
          request.getRequestDispatcher("/Access_Denied").forward(request, response);
      }
      
    2. In the controller method, check whether the original request is for the login page. If so, show an appropriate message within the login page.

      if ("/login".equals(request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH))) {
          model.addAttribute("error", "An invalid security token has been detected. Please try again.");
          return "login.jsp";
      } else {
          return "accessDenied.jsp";
      }
      

    With this approach, user will be able to retry the login without the need to refresh.

    0 讨论(0)
  • 2021-02-01 09:35

    Recommended solution

    I would say that you should not disable csrf tokens on a production site. You may make session (and thus the csrf token) last longer (but it usually should not last longer than a day, especially for not-logged-in users as it is a DOS vector), but the real solution may be to automatically refresh the login page when the csrf token expires. You may use a

    <META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">
    

    in your login page header. If the user lets the login page sit for hours, it should not bother him that the page got refreshed.

    Second solution

    A possible solution which does not require you to actually store sessions but allows for infinite timeout is that you can generate your csrf tokens with hashing from the session id and a server-side secret:

    csrf = hash(sessionid+secret)
    

    Note however that you need to really dig and override spring-security internal mechanisms, namely:

    • re-creating anonymous sessions on the fly if a request arrives and no such session exists
    • re-creating the csrf token on the fly from the session id

    And choose a very secure hashing algorithm, preferably sha-512.

    Third solution

    You could have a small javascript that calls a no-op page on your server regularly (just before the session timeout), thus extending your session. This results in infinite session timeout only if the browser is on all the time, so the DOS aspect is mitigated.

    Ok, one last solution

    You can alter the CSRF token checking code, and disable it for the login page. This is actually synonymous with the second solution, but is specific for the login page, not generally for all anonymous sessions.

    You can do this e.g. by setting a custom RequestMatcher in HttpSecurity:

    http.csrf().requireCsrfProtectionMatcher(new MyCsrfRequestMatcher());
    ...
    class MyCsrfRequestMatcher implements RequestMatcher {
        @Override
        public boolean matches(HttpServletRequest request) {
            return !request.getServletPath().equals("/login");
        }
    }
    
    0 讨论(0)
提交回复
热议问题