Страница входа в Spring Security с React

Страница входа в Spring Security с React

1. обзор

React - это компонентная библиотека JavaScript, созданная Facebook. С React мы можем легко создавать сложные веб-приложения. В этой статье мы собираемся заставить Spring Security работать вместе со страницей входа в React.

Мы воспользуемся преимуществами существующих конфигураций Spring Security из предыдущих примеров. Итак, мы будем опираться на предыдущую статью о созданииForm Login with Spring Security.

2. Настроить React

Во-первых, давайтеuse the command-line tool create-react-app to create an application, выполнив команду «create-react-app react”.

Вreact/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"
    }
}

Затем мыuse 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
            
        
    

Последнюю версию плагина можно найтиhere.

Когда мы запускаемmvn compile, этот плагин загрузитnode иnpm, установит все зависимости модуля узла и построит для нас проект реакции.

Есть несколько свойств конфигурации, которые мы должны объяснить здесь. Мы указали версииnode иnpm, чтобы плагин знал, какую версию загружать.

Наша страница входа в React в Spring будет служить статической, поэтому мы используем «src/main/webapp_ / WEB-INF / view / response_» в качестве рабочего каталогаnpm.

3. Конфигурация Spring Security

Прежде чем мы углубимся в компоненты React, мы обновляем конфигурацию Spring для обслуживания статических ресурсов нашего приложения 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 вместо динамически обслуживаемого JSP.

Далее мы обновляем конфигурацию Spring Security, чтобы разрешить доступ к этим статическим ресурсам.

Вместо использования“login.jsp”, как мы использовали в статьеthe previous form login, здесь мы используем“index.html” в качестве нашей страницыLogin:

@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")
          //...
    }
}

Как видно из фрагмента выше, когда мы отправляем данные формы в «/perform_login», Spring перенаправит нас на «/homepage.html», если учетные данные совпадают успешно, и на «/index.html?error=true» в противном случае.

4. Реагировать Компоненты

А теперь займемся React. Мы создадим и будем управлять формой входа в систему с помощью компонентов.

Обратите внимание, что мы будем использовать синтаксис ES6 (ECMAScript 2015) для создания нашего приложения.

4.1. вход

Начнем с компонентаInput, который поддерживает элементы<input /> формы входа вreact/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

Как видно выше, мы оборачиваем элемент<input /> в компонент, управляемый React, чтобы иметь возможность управлять его состоянием и выполнять проверку полей.

React предоставляет способ проверки типов с помощьюPropTypes. В частности, мы используемInput.propTypes = \{…} для проверки типа свойств, переданных пользователем.

Обратите внимание, что проверкаPropType работает только для разработки. ПроверкаPropType - это проверка того, что все предположения, которые мы делаем о наших компонентах, выполняются.

Лучше иметь его, чем удивляться случайным сбоям в производстве.

4.2. Form

Затем мы создадим общий компонент формы в файлеForm.js, который объединит несколько экземпляров нашего компонентаInput, на котором мы сможем основать нашу форму входа.

В компонентеForm мы берем атрибуты элементов HTML<input/> и создаем из них компонентыInput.

Затем компонентыInput и сообщения об ошибках проверки вставляются вForm:

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

Теперь давайте посмотрим, как мы управляем ошибками проверки полей и ошибками входа:

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

В этом фрагменте мы определяем функциюhandleError для управления ошибочным состоянием формы. Напомним, что мы также использовали его для проверки поляInput. Actually, handleError() is passed to the Input Components as a callback in the render() function.

Мы используемrenderError() для создания элемента сообщения об ошибке. Обратите внимание, что конструкторForm’s использует свойствоerror. Это свойство указывает, если действие входа не удается.

Затем идет обработчик отправки формы:

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))
        }
    }
}

Мы оборачиваем все поля формы вFormData и отправляем его на сервер, используяfetch API.

Не будем забывать, что в нашей форме входа есть символыsuccessUrl иfailureUrl, что означает, что независимо от того, был ли запрос успешным или нет, для ответа потребуется перенаправление.

Вот почему нам нужно обрабатывать перенаправление в обратном вызове ответа.

4.3. Рендеринг форм

Теперь, когда мы настроили все необходимые компоненты, мы можем продолжать помещать их в DOM. Базовая структура HTML следующая (найдите ее вreact/public/index.html):



  
    
  
  

    

Наконец, мы визуализируем форму в<div/> с идентификатором «container” вreact/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'))

Итак, наша форма теперь содержит два поля ввода:username иpassword, а также кнопку отправки.

Здесь мы передаем дополнительный атрибутerror компонентуForm, потому что мы хотим обработать ошибку входа в систему после перенаправления на URL-адрес ошибки:/index.html?error=true.

form login error

Теперь мы закончили создание приложения для входа в Spring Security с помощью React. Последнее, что нам нужно сделать, это запуститьmvn compile.

Во время этого процесса плагин Maven поможет собрать наше приложение React и собрать результат сборки вsrc/main/webapp/WEB-INF/view/react/build.

5. Заключение

В этой статье мы рассказали, как создать приложение для входа на React и позволить ему взаимодействовать с серверной частью Spring Security. Более сложное приложение будет включать переход между состояниями и маршрутизацию с использованиемReact Router илиRedux, но это выходит за рамки данной статьи.

Как всегда, полную реализацию можно найти вover on Github. Чтобы запустить его локально, выполнитеmvn jetty:run в корневой папке проекта, после чего мы сможем получить доступ к странице входа в React вhttp://localhost:8080.