miércoles, 1 de marzo de 2017

Seguridad con SpringBoot y CSRF

La seguridad con con Springboot es realmente sencilla.
Para activar la Basic Html, simplemente creamos un conjunto de clases de esta forma:

@EnableWebSecurity
@Configuration
@EnableSpringHttpSession
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${com.pmj.dyo.configuration.security.user}")
    private String user;

    @Value("${com.pmj.dyo.configuration.security.password}")
    private String password;
    
    @Value("${com.pmj.dyo.configuration.csrf.login}")
    private String loginPage;
    
    @Value("${com.pmj.dyo.configuration.csrf.allow}")
    private String[] patterns;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers(patterns).permitAll().and().csrf().disable().httpBasic().and()
                .addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
                .addFilterAfter(new CsrfGrantingFilter(loginPage), CsrfFilter.class);
    }

    private Filter csrfFilter(String[] patterns) {
        CsrfFilter csrfFilter = new CsrfFilter(csrfTokenRepository());
        csrfFilter.setRequireCsrfProtectionMatcher(csrfProtectionMatcher(patterns));
        return csrfFilter;
    }

    private NoAntPathRequestMatcher csrfProtectionMatcher(String[] patterns) {
        return new NoAntPathRequestMatcher(patterns);
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName(X_XSRF_TOKEN);
        return repository;
    }

    /**
     * bCryptPasswordEncoder
     * 
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * configureGlobal
     * 
     * @param auth
     * @throws Exception
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(user).password(password).roles("USER");
    }

    /**
     * sessionRepository
     * 
     * @return
     */
    @Bean
    @SuppressWarnings("rawtypes")
    public SessionRepository sessionRepository() {
        return new MapSessionRepository();
    }

    /**
     * sessionStrategy
     * 
     * @return
     */
    @Bean
    public HeaderHttpSessionStrategy sessionStrategy() {
        return new HeaderHttpSessionStrategy();
    }

    /**
     * @return the user
     */
    public String getUser() {
        return user;
    }

    /**
     * @param user
     *            the user to set
     */
    public void setUser(String user) {
        this.user = user;
    }

    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }

    /**
     * @param password
     *            the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////


public class CsrfGrantingFilter implements Filter {

    public static final String X_XSRF_TOKEN = "X-XSRF-TOKEN";

    private String loginPage;

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * Login Page injection
     * @param loginPage
     */
    public CsrfGrantingFilter(String loginPage) {
        this.loginPage = loginPage;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        CsrfToken csrf = (CsrfToken) servletRequest.getAttribute(CsrfToken.class.getName());
        String token = csrf.getToken();
        if (token != null && isAuthenticating(servletRequest)) {
            servletRequest.setAttribute(X_XSRF_TOKEN, true);
        } else {
            servletRequest.setAttribute(X_XSRF_TOKEN, false);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean isAuthenticating(ServletRequest servletRequest) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        return request.getRequestURI().equals(loginPage);
    }

    @Override
    public void destroy() {
        LogUtil.info(log, this.toString());
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LogUtil.info(log, filterConfig.toString());
    }
}

///////////////////////////////////////////////////////////////////////////////

@RequestMapping(method = POST, path = "", produces = APPLICATION_JSON_VALUE)
    public Map login(@RequestBody UserEntry userEntry, ServletRequest servletRequest,
            ServletResponse servletResponse) {
        boolean xsrfAvailable = (boolean) servletRequest.getAttribute(X_XSRF_TOKEN);
        Authorization aut = client.login(userEntry);
        Map resolve = new HashMap(); 
        if (aut != null && aut.isAccepted() && xsrfAvailable) {
            CsrfToken csrf = (CsrfToken) servletRequest.getAttribute(CsrfToken.class.getName());
            String token = csrf.getToken();
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            Cookie cookie = new Cookie(X_XSRF_TOKEN, token);
            cookie.setPath("/");
            cookie.setSecure(true);
            response.addCookie(cookie);
            resolve.put(STATUS, AUTHORIZED);
            resolve.put(USERNAME, username);
            resolve.put(PASSWORD, password);
            resolve.put(ROLE, aut.getRole());
            resolve.put(TOKEN, token);
        } else {
            resolve.put(STATUS, UNAUTHORIZED);
        }
        return resolve;
    }


Básicamente lo que sucede en este ejemplo es lo siguiente:
  1.  El punto de control que genera el Cross Site Request Forgery es /login
  2. Si el login no es efectivo el CSRF no aparece como set en el cookie
  3. Ahora bien el toque esta en que el body también presenta el basic autentification y el CSRF. De esta forma es mas sencillo que el Javascript almacene los datos.
  4. Cada nuevo request debe enviar los Basic Login y el CSRF.
Dentro de la aplicacion pasa esto:
  1.  La aplicacion fija el toking en el login. 
  2. Si cualquier otro request que no este en la lista de permitidos es invocada. Revisa el Basic Aut y el CSRF toking. La clase que genera los toking es CsrfGrantingFilter.

AEM hablemos del arquetipo 11

Cuando creamos un proyecto con AEM. Siempre es importante saber que arquetipo estamos usando. Pues esto me determinara que source, herramien...