Перейти к основному содержимому

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

· 7 мин. чтения

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 .

./7a1a9db81c141d65261ddb3ff676e993.png

Теперь мы закончили создание приложения для входа в 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 .