1. Обзор
React — это основанная на компонентах библиотека JavaScript, созданная Facebook. С React мы можем легко создавать сложные веб-приложения. В этой статье мы собираемся заставить Spring Security работать вместе со страницей входа React.
Мы воспользуемся преимуществами существующих конфигураций Spring Security из предыдущих примеров. Итак, мы будем опираться на предыдущую статью о создании формы входа с помощью Spring Security .
2. Настройте Реакт
Во-первых, давайте воспользуемся инструментом командной строки create-react-app для создания приложения , выполнив команду « 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"
}
}
Затем мы воспользуемся frontend-maven-plugin , чтобы построить наш проект React с помощью Maven:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<configuration>
<nodeVersion>v8.11.3</nodeVersion>
<npmVersion>6.1.0</npmVersion>
<workingDirectory>src/main/webapp/WEB-INF/view/react</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
</execution>
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
Последнюю версию плагина можно найти здесь .
Когда мы запустим mvn compile
, этот плагин загрузит node
и npm
, установит все зависимости модуля node и создаст для нас реактивный проект.
Здесь нужно объяснить несколько свойств конфигурации. Мы указали версии node
и npm
, чтобы плагин знал, какую версию скачивать.
Наша страница входа в React будет статической страницей в Spring, поэтому мы используем « src/main/
webapp /WEB-INF/view/react
» в качестве рабочего каталога npm .
3. Конфигурация безопасности Spring
Прежде чем мы углубимся в компоненты 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");
}
}
Обратите внимание, что мы добавляем страницу входа «index.html»
в качестве статического ресурса вместо динамически обслуживаемого JSP.
Затем мы обновляем конфигурацию Spring Security, чтобы разрешить доступ к этим статическим ресурсам.
Вместо использования «login.jsp»
, как мы делали в предыдущей статье о входе в форму, здесь мы используем «index.html»
в качестве нашей страницы входа :
@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 />
формы входа в 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 {...opts} value={this.state.value}
onChange={this.inputChange} className={this.state.className} />
)
}
}
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.js
, который объединяет несколько экземпляров нашего компонента ввода
, на основе которого мы можем создать нашу форму входа.
В компоненте Form мы берем атрибуты элементов HTML
<input/>
и создаем из них компоненты ввода .
Затем компоненты ввода
и сообщения об ошибках проверки вставляются в форму:
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) => (
<Input key={index} name={name} placeholder={placeholder} type={type} value={value}
className={type==='submit'? className : ''} handleError={this.handleError} />
)
)
const errors = this.renderError()
return (
<form {...this.props} onSubmit={this.handleSubmit} ref={fm => {this.form=fm}} >
{inputs}
{errors}
</form>
)
}
}
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 <div className="error">{errmsg}</div>
}
}
//...
}
В этом фрагменте мы определяем функцию handleError
для управления состоянием ошибки формы. Напомним, что мы также использовали его для проверки поля ввода .
На самом деле, handleError()
передается компонентам ввода
как обратный вызов в функции render ()
.
Мы используем renderError()
для создания элемента сообщения об ошибке. Обратите внимание, что конструктор формы
использует свойство ошибки .
Это свойство указывает, происходит ли сбой при входе в систему.
Затем идет обработчик отправки формы:
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
):
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<div id="root">
<div id="container"></div>
</div>
</body>
</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(
<Form {...props} error={params.get('error')} />,
document.getElementById('container'))
Итак, наша форма теперь содержит два поля ввода: имя пользователя
и пароль
, а также кнопку отправки.
Здесь мы передаем дополнительный атрибут ошибки компоненту
формы
, потому что мы хотим обработать ошибку входа после перенаправления на URL-адрес сбоя: /index.html?error=true
.
Теперь мы закончили создание приложения для входа в Spring Security с использованием React. Последнее, что нам нужно сделать, это запустить mvn compile
.
В процессе плагин Maven поможет собрать наше приложение React и собрать результат сборки в src/main/webapp/WEB-INF/view/react/build
.
5. Вывод
В этой статье мы рассмотрели, как создать приложение для входа в React и позволить ему взаимодействовать с серверной частью Spring Security. Более сложное приложение будет включать переход состояний и маршрутизацию с использованием React Router или Redux , но это выходит за рамки этой статьи.
Как всегда, полную реализацию можно найти на GitHub . Чтобы запустить его локально, выполните mvn jetty:run
в корневой папке проекта, после чего мы сможем получить доступ к странице входа в систему React по адресу http://localhost:8080
.