Mostrando entradas con la etiqueta Springboot. Mostrar todas las entradas
Mostrando entradas con la etiqueta Springboot. Mostrar todas las entradas

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.

martes, 21 de febrero de 2017

Java Poi con Maven y Springboot Restful (Usando imagenes)

Este articulo no es un "tutorial". Es un conjunto de notas de mi experiencia.
Si vas a adentrarte en este articulo. Debes conocer como usar POI. Maven y Springboot Restfull.

 Lo primero que debemos hacer es configurar el POM del maven para usar  todas las características de excel. Claramente algunas no son necesarias. Pero es mejor tenerlas para usar TODO lo que Excel ofrece:


      dom4j
      dom4j
      1.6.1
  
  
   org.apache.poi
   poi
   3.9
  
  
   org.apache.poi
   poi-ooxml
   3.9
  
  
      org.apache.commons
      commons-collections4
      4.1
  
  
      org.apache.xmlbeans
      xmlbeans
      2.3.0
  

Ahora bien para generar reportes POI ofrece varias características principalmente varias vistas de distintos tipos. Para no complicarse vamos a usar lo que SpringBoot ofrece.


No es recomendable... generar los reportes como stream dentro de los HttpServletResponse . Porque se puede cortar el request y el reporte quedar corrupto. Usar siempre las vistas.

//Lo primero debemos escojer un view que se ajuste a las necesidades. Al final hablare de los distintos tipos
public class ExcelBuilder extends AbstractXssfStreamingView {

    @Override
    public void buildExcelDocument(Map model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //Aqui ya vienen inicializado el workbook.
        //La unica funcion es implementar. Seria; Crear el sheet, crear filas, celdas, poner estilos, etc
        //No debemos preocuparnos por escribir el workbook en el response. Solo dejemos que el metodo termine.
        //Y Spring sabe que debe tomar el workbook y generar un archivo.
    }

}

//Debemos configurar que la vista sea usada para los endpoint
//Sencillamente podemos usar esto:
@Configuration
public class ExcelBuilderConfig {
    
    @Bean
    public ExcelBuilder ExcelBuilder() {
        return new ExcelBuilder();
    }

}

//Ahora bien solo resta usarlo en algun endpoint
@Api(value = "contest", description = "the contest API")
public interface ContestApi {

    /**
     * contestApplicantExport
     *
     * @param contest
     * @param brandid
     * @return
     */
    @ApiOperation(value = "", notes = "Returns a excel file report according to the information provided (A5:ContestApplicantExport)")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "gallery response"),
            @ApiResponse(code = 200, message = "unexpected error")})
    @RequestMapping(value = "/contest/{contest}/{brandid}", produces = {
            "application/ms-excel"}, method = RequestMethod.GET)
    @org.springframework.web.bind.annotation.GetMapping("/dyo-config")
    ModelAndView contestApplicantExport(
            @ApiParam(value = "Filter based on a specific contest", required = true) @PathVariable("contest") String contest,
            @ApiParam(value = "Filter based on a specific brand", required = true) @PathVariable("brandid") String brandid,
            @ApiParam(value = "HttpServletResponse to setup the excel ouput", required = true) HttpServletResponse response);
}



@Configuration
@RestController
public class ContestApiController implements ContestApi {


    @Override
    public ModelAndView contestApplicantExport(
            @ApiParam(value = "Filter based on a specific contest", required = true) @PathVariable("contest") String contest,
            @ApiParam(value = "Filter based on a specific brand", required = true) @PathVariable("brandid") String brandid,
            @ApiParam(value = "HttpServletResponse to setup the excel ouput", required = true) HttpServletResponse response) {

        Book book = /*Cualquier Objecto*/
        return new ModelAndView("ExcelBuilder", "book", book); //Note que "book" es el dato que se puede obtener de la vista
    }
}

Los diferentes tipos de vistas presentes generan diferentes tipos de workbook.
Cada una con características diferentes:
Podemos mencionar XSSFWorkbook and SXSSFWorkbook.

La clase AbstractXssfStreamingViewes funcional para archivos muy grandes. Sin embargo tiene la tendencia a no funcionar de buena manera con las imágenes. Puede que te tengas que enfrentar a Streaching o a resize ridículos. Después de muchos intentos y de canas verdes. Descubrí que si uno quiere insertar imágenes. La mejor forma es usando XSSFWorkbook.

Para eso escribí mi propia versión de un AbstractXlsxView.

//Simplemente hacemos esto:
public abstract class AbstractXssfStreamingView extends AbstractXlsxView {

    @Override
    protected XSSFWorkbook createWorkbook(Map model, HttpServletRequest request) {
        return new XSSFWorkbook();
    }

}

//Y lo utilizamos:
public class ExcelBuilder extends AbstractXssfStreamingView {

    @Override
    public void buildExcelDocument(Map model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Book book = (Book) model.get("book"); //Usar de referencia al ejemplo de arriba para obtener los datmos del model
        //Tu codigo aqui
    }

}

//En el codigo de ejemplo para agregar imagenes creo un pequeno factory.
//La implementacion es por un conjunto de intefacez de esta manera:

@FunctionalInterface
public interface CellProduceInterface {

    /**
     * update
     * 
     * @param excelCell
     * @param data
     * @param workbook
     * @param sheet
     * @param point
     */
    public void update(org.apache.poi.ss.usermodel.Cell excelCell, Cell data, Workbook workbook, Sheet sheet, Point point);

}

//Y su implementacion
//Notas:
//El excelCell es el cell de POI
//El data es el modelo de la informacion de la celda
//Woorkbook asociado con la vista 
//Sheet actual en que que excelCell esta presente
public class ImageCellProduce implements CellProduceInterface {

    private static final Logger log = LoggerFactory.getLogger(ImageCellProduce.class);

    @Override
    public void update(org.apache.poi.ss.usermodel.Cell excelCell, Cell data, Workbook workbook, Sheet sheet,
            Point point) {
        String path = (String) data.getValue();
        if (path != null) {
            byte[] bytes = ExcelUtil.convertImagePathToBytes(path);
            if (bytes.length > 0) {
                int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
                CreationHelper helper = workbook.getCreationHelper();
                Drawing drawing = sheet.createDrawingPatriarch();
                ClientAnchor anchor = helper.createClientAnchor();
                //Col and Cell pos
                anchor.setCol1(point.y);
                anchor.setRow1(point.x);
                //Height is not possible without stretched the image
                Picture pict = drawing.createPicture(anchor, pictureIdx);
                if (data.getWidth() != -1) {
                    sheet.setColumnWidth(point.y, ExcelUtil.calculatePoiWidthSize(data.getWidth()));
                    data.setWidth(-1);//Asi no se vuelve a aplicar el estilo mas adelante
                }
                if (data.getHeight() != -1) {
                    excelCell.getRow().setHeight((short) ExcelUtil.calculatePoiWidthSize(data.getHeight()));
                    data.setHeight(-1); //Asi no se vuelve a aplicar el estilo mas adelante
                }
                data.setAutoSize(false);
                pict.resize(); //Como es XSSFWorkbook no deberia presentar problemas con el size y mantiene el original
            }
        } else {
            log.info("ImageCellProduce: Ignoring image on " + point);
        }
    }

}

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...