Página de login do Spring Security com React

Página de login do Spring Security com React

1. Visão geral

React é uma biblioteca JavaScript baseada em componente construída pelo Facebook. Com o React, podemos criar aplicativos da Web complexos com facilidade. Neste artigo, faremos Spring Security trabalhar junto com uma página React Login.

Tiraremos vantagem das configurações existentes do Spring Security de exemplos anteriores. Então, vamos construir sobre um artigo anterior sobre a criação de umForm Login with Spring Security.

2. Configurar o React

Primeiro, vamosuse the command-line tool create-react-app to create an application executando o comando “create-react-app react”.

Teremos uma configuração como a seguinte emreact/package.json:

{
    "name": "react",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
        "react": "^16.4.1",
        "react-dom": "^16.4.1",
        "react-scripts": "1.1.4"
    },
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
    }
}

Então, vamosuse the frontend-maven-plugin to help build our React project with Maven:


    com.github.eirslett
    frontend-maven-plugin
    1.6
    
        v8.11.3
        6.1.0
        src/main/webapp/WEB-INF/view/react
    
    
        
            install node and npm
            
                install-node-and-npm
            
        
        
            npm install
            
                npm
            
        
        
            npm run build
            
                npm
            
            
                run build
            
        
    

A última versão do plugin pode ser encontradahere.

Quando executarmosmvn compile, este plugin irá baixarnodeenpm, instalar todas as dependências do módulo de nó e construir o projeto react para nós.

Existem várias propriedades de configuração que precisamos explicar aqui. Especificamos as versões denodeenpm, para que o plugin saiba qual versão baixar.

Nossa página de login do React servirá como uma página estática no Spring, então usamos “src/main/webapp_ / WEB-INF / view / react_” como o diretório de trabalho denpm.

3. Configuração de segurança da primavera

Antes de mergulharmos nos componentes React, atualizamos a configuração do Spring para atender aos recursos estáticos do nosso aplicativo React:

@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(
      ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/static/**")
          .addResourceLocations("/WEB-INF/view/react/build/static/");
        registry.addResourceHandler("/*.js")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/*.json")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/*.ico")
          .addResourceLocations("/WEB-INF/view/react/build/");
        registry.addResourceHandler("/index.html")
          .addResourceLocations("/WEB-INF/view/react/build/index.html");
    }
}

Note that we add the login page “index.html” as a static resource em vez de um JSP servido dinamicamente.

Em seguida, atualizamos a configuração do Spring Security para permitir o acesso a esses recursos estáticos.

Em vez de usar“login.jsp” como fizemos no artigothe previous form login, aqui usamos“index.html” como nossa páginaLogin:

@Configuration
@EnableWebSecurity
@Profile("!https")
public class SecSecurityConfig
  extends WebSecurityConfigurerAdapter {

    //...

    @Override
    protected void configure(final HttpSecurity http)
      throws Exception {
        http.csrf().disable().authorizeRequests()
          //...
          .antMatchers(
            HttpMethod.GET,
            "/index*", "/static/**", "/*.js", "/*.json", "/*.ico")
            .permitAll()
          .anyRequest().authenticated()
          .and()
          .formLogin().loginPage("/index.html")
          .loginProcessingUrl("/perform_login")
          .defaultSuccessUrl("/homepage.html",true)
          .failureUrl("/index.html?error=true")
          //...
    }
}

Como podemos ver no snippet acima, quando postarmos os dados do formulário em “/perform_login“, o Spring nos redirecionará para “/homepage.html” se as credenciais corresponderem com sucesso e para “/index.html?error=true” caso contrário.

4. Reagir componentes

Agora vamos sujar as mãos no React. Vamos construir e gerenciar um login de formulário usando componentes.

Observe que usaremos a sintaxe ES6 (ECMAScript 2015) para construir nosso aplicativo.

4.1. Entrada

Vamos começar com um componenteInput que apóia os elementos<input /> do formulário de login emreact/src/Input.js:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Input extends Component {
    constructor(props){
        super(props)
        this.state = {
            value: props.value? props.value : '',
            className: props.className? props.className : '',
            error: false
        }
    }

    //...

    render () {
        const {handleError, ...opts} = this.props
        this.handleError = handleError
        return (
          
        )
    }
}

Input.propTypes = {
  name: PropTypes.string,
  placeholder: PropTypes.string,
  type: PropTypes.string,
  className: PropTypes.string,
  value: PropTypes.string,
  handleError: PropTypes.func
}

export default Input

Conforme visto acima, envolvemos o elemento<input /> em um componente controlado pelo React para poder gerenciar seu estado e realizar a validação de campo.

O React fornece uma maneira de validar os tipos usandoPropTypes. Especificamente, usamosInput.propTypes = \{…} para validar o tipo de propriedades transmitidas pelo usuário.

Observe que a validação dePropType funciona apenas para desenvolvimento. A validação dePropType é para verificar se todas as suposições que estamos fazendo sobre nossos componentes estão sendo atendidas.

É melhor tê-lo em vez de ficar surpreso com soluços aleatórios na produção.

4.2. Form

A seguir, vamos construir um componente de formulário genérico no arquivoForm.js que combina várias instâncias de nosso componenteInput no qual podemos basear nosso formulário de login.

No componenteForm, pegamos atributos de elementos HTML<input/> e criamos componentesInput a partir deles.

Em seguida, os componentesInput e as mensagens de erro de validação são inseridos noForm:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Input from './Input'

class Form extends Component {

    //...

    render() {
        const inputs = this.props.inputs.map(
          ({name, placeholder, type, value, className}, index) => (
            
          )
        )
        const errors = this.renderError()
        return (
            
{this.form=fm}} > {inputs} {errors}
) } } Form.propTypes = { name: PropTypes.string, action: PropTypes.string, method: PropTypes.string, inputs: PropTypes.array, error: PropTypes.string } export default Form

Agora vamos dar uma olhada em como gerenciamos erros de validação de campo e erro de login:

class Form extends Component {

    constructor(props) {
        super(props)
        if(props.error) {
            this.state = {
              failure: 'wrong username or password!',
              errcount: 0
            }
        } else {
            this.state = { errcount: 0 }
        }
    }

    handleError = (field, errmsg) => {
        if(!field) return

        if(errmsg) {
            this.setState((prevState) => ({
                failure: '',
                errcount: prevState.errcount + 1,
                errmsgs: {...prevState.errmsgs, [field]: errmsg}
            }))
        } else {
            this.setState((prevState) => ({
                failure: '',
                errcount: prevState.errcount===1? 0 : prevState.errcount-1,
                errmsgs: {...prevState.errmsgs, [field]: ''}
            }))
        }
    }

    renderError = () => {
        if(this.state.errcount || this.state.failure) {
            const errmsg = this.state.failure
              || Object.values(this.state.errmsgs).find(v=>v)
            return 
{errmsg}
} } //... }

Neste trecho, definimos a funçãohandleError para gerenciar o estado de erro do formulário. Lembre-se de que também o usamos para validação de campoInput. Actually, handleError() is passed to the Input Components as a callback in the render() function.

UsamosrenderError() para construir o elemento de mensagem de erro. Observe que o construtorForm’s consome uma propriedadeerror. Esta propriedade indica se a ação de login falha.

Depois, vem o manipulador de envio de formulários:

class Form extends Component {

    //...

    handleSubmit = (event) => {
        event.preventDefault()
        if(!this.state.errcount) {
            const data = new FormData(this.form)
            fetch(this.form.action, {
              method: this.form.method,
              body: new URLSearchParams(data)
            })
            .then(v => {
                if(v.redirected) window.location = v.url
            })
            .catch(e => console.warn(e))
        }
    }
}

Envolvemos todos os campos do formulário emFormDatae enviamos para o servidor usandofetch API.

Não vamos esquecer que nosso formulário de login vem comsuccessUrlefailureUrl, o que significa que não importa se a solicitação foi bem-sucedida ou não, a resposta exigiria um redirecionamento.

É por isso que precisamos lidar com o redirecionamento no retorno de chamada de resposta.

4.3. Renderização de formulários

Agora que configuramos todos os componentes de que precisamos, podemos continuar a colocá-los no DOM. A estrutura HTML básica é a seguinte (encontre-a emreact/public/index.html):



  
    
  
  

    

Finalmente, vamos renderizar o Form em<div/> com id “container” emreact/src/index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import Form from './Form'

const inputs = [{
  name: "username",
  placeholder: "username",
  type: "text"
},{
  name: "password",
  placeholder: "password",
  type: "password"
},{
  type: "submit",
  value: "Submit",
  className: "btn"
}]

const props = {
  name: 'loginForm',
  method: 'POST',
  action: '/perform_login',
  inputs: inputs
}

const params = new URLSearchParams(window.location.search)

ReactDOM.render(
  
, document.getElementById('container'))

Portanto, nosso formulário agora contém dois campos de entrada:username epassword, e um botão de envio.

Aqui, passamos um atributoerror adicional para o componenteForm porque queremos tratar o erro de login após o redirecionamento para a URL de falha:/index.html?error=true.

form login error

Agora terminamos de construir um aplicativo de login Spring Security usando React. A última coisa que precisamos fazer é executarmvn compile.

Durante o processo, o plugin Maven ajudará a construir nosso aplicativo React e coletar o resultado da construção emsrc/main/webapp/WEB-INF/view/react/build.

5. Conclusão

Neste artigo, cobrimos como construir um aplicativo de login React e deixá-lo interagir com um back-end Spring Security. Uma aplicação mais complexa envolveria transição de estado e roteamento usandoReact Router ouRedux, mas isso estaria além do escopo deste artigo.

Como sempre, a implementação completa pode ser encontradaover on Github. Para executá-lo localmente, executemvn jetty:run na pasta raiz do projeto, então podemos acessar a página de login do React emhttp://localhost:8080.