Статьи

Создайте приложение CRUD с React, Spring Boot и аутентификацией пользователя

Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!

React — одна из самых популярных библиотек для создания веб-приложений. С Spring Boot проще, чем когда-либо, создать серверную часть CRUD для вашего React-приложения . В этом руководстве мы свяжем их вместе, а затем используем Stormpath для добавления протоколов аутентификации и авторизации.

Мы начнем с создания статического представления данных с использованием React. Затем мы создадим REST-сервер с Spring Boot, свяжем его и добавим безопасность пользователя с помощью Stormpath. Все должно быть просто, даже если вы никогда раньше не использовали React.

Исходный код, который поддерживает этот пост, можно найти в этом репозитории GitHub .

Служить фронту

Обычно приложения React обслуживаются с использованием Node.js, но если вы являетесь разработчиком Java, вам, вероятно, будет очень удобно использовать этот подход Spring Boot.

Сначала вы поместите все приложение в один файл index.html . Чтобы указать Spring Boot, чтобы он служил в качестве домашней страницы, вы можете использовать аннотацию @Controller .

1
2
3
4
5
6
7
8
@Controller
public class HomeController {
  
    @RequestMapping(value = "/")
    public String index() {
        return "index.html";
    }
}

Создайте пустой каталог и поместите приведенный выше код в src/main/java/tutorial/HomeController.java . Spring Boot будет искать src/main/resources/static/index.html при загрузке сайта.

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<head>
    <title>React + Spring</title>
</head>
<body>
</body>
</html>

Создайте pom.xml и класс приложения Spring Boot. Используйте следующее для вашего POM.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
    </parent>
  
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
  
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Поместите следующее в src/main/java/tutorial/Application.java .

1
2
3
4
5
6
7
@SpringBootApplication
public class Application {
  
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Когда вы запускаете сервер с помощью mvn spring-boot:run и посещаете localhost:8080 вы должны увидеть пустую страницу с заголовком «React + Spring».

5563044

Удалить перезагрузки

Обычно вам придется перезагружать сервер каждый раз, когда вы вносите изменения в интерфейс, что является проблемой. Использование инструментов разработчика Spring Boot позволяет нам обойти это. Добавьте следующую зависимость к вашему POM.

1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

Также добавьте эту конфигурацию в ваш плагин Spring Boot Maven:

1
2
3
<configuration>
    <addResources>true</addResources>
</configuration>

Теперь, когда вы вносите изменения в свое приложение или перекомпилируете какие-либо классы, оно должно обновляться при обновлении браузера.

Реагировать на Barebones HTML

Реагировать! Самая простая страница React имеет три вещи: корневой элемент, импорт JavaScript и тег скрипта.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
    <title>React + Spring</title>
</head>
<body>
    <div id='root'></div>
  
    <script src="https://fb.me/react-15.0.1.js"></script>
    <script src="https://fb.me/react-dom-15.0.1.js"></script>
  
    <script type="text/babel"></script>
</body>
</html>

Корневой элемент — это место, где React вставит наш HTML-код. При импорте используются три библиотеки — две для самого React, а другая для перевода нашего шаблона представления с помощью babel .

Примечание: для удобства тестирования мы загружаем библиотеки с использованием CDN, но обычно вы используете что-то вроде веб-пакета, чтобы объединить весь ваш Javascript в один файл.

Теперь поместите ваш код React внутри тега script.

Реагировать Основы

Как описано в уроке мышления в реакции , вы должны начать кодировать свое приложение, разбив интерфейс на components .

1
2
3
4
<script type="text/babel">
var Employee = React.createClass({});
var EmployeeTable = React.createClass({});
</script>

Здесь вы создали два — один для таблицы сотрудников, а другой для записи сотрудников. Затем каждому компоненту нужна функция рендеринга, которая описывает HTML для генерации.

01
02
03
04
05
06
07
08
09
10
11
12
<script type="text/babel">
var Employee = React.createClass({
  render: function() {
    return (<div>employee</div>);
  }
});
var EmployeeTable = React.createClass({
  render: function() {
    return (<div>employee table</div>);
  }
});
</script>

Здесь приходит компилятор Babel для преобразования HTML-кода в правильные операторы React. Обратите внимание, как теги div возвращаются из оператора render .

Вы должны указать React вставить HTML родительского компонента в корневой элемент. Это делается с ReactDOM.render метода ReactDOM.render .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<script type="text/babel">
var Employee = React.createClass({
  render: function() {
    return (<div>employee</div>);
  }
});
var EmployeeTable = React.createClass({
  render: function() {
    return (<div>employee table</div>);
  }
});
  
ReactDOM.render(
  <EmployeeTable />, document.getElementById('root')
);
</script>

Обновив браузер, вы увидите текстовый элемент, который вы создали.

26089170

Чтобы увидеть HTML React, вставленный в корневой элемент, вы можете использовать инспектор браузера (Ctrl-Shift-J в Chrome).

22301758

Связывание компонентов вместе

Теперь, когда у вас есть компоненты, давайте свяжем их вместе. Вы можете начать с попытки рендеринга жестко закодированных данных; Вы будете использовать сервер REST позже.

Над командой ReactDOM введите следующее:

1
2
3
4
5
6
var EMPLOYEES = [
  {name: 'Joe Biden', age: 45, years: 5},
  {name: 'President Obama', age: 54, years: 8},
  {name: 'Crystal Mac', age: 34, years: 12},
  {name: 'James Henry', age: 33, years: 2}
];

Затем добавьте employees={EMPLOYEES} когда вы создаете таблицу.

1
2
3
ReactDOM.render(
  <EmployeeTable employees={EMPLOYEES} />, document.getElementById('root')
);

Как и следовало ожидать, это передает данные в переменную с именем employees . Внутри EmployeeTable вы можете получить к нему доступ, используя this.props . Давайте использовать это для создания таблицы со строкой для каждого сотрудника.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
var EmployeeTable = React.createClass({
  render: function() {
    var rows = [];
    this.props.employees.forEach(function(employee) {
      rows.push(<Employee employee={employee} />);
    });
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th><th>Age</th><th>Years</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>);
  }
});

Это создает новый класс Employee для каждого элемента данных (устанавливает атрибут employee ) и помещает его в массив. Затем {rows} добавляет необходимый HTML-код из дочернего класса.

Теперь все, что вам нужно сделать, это обновить метод рендеринга на Employee.

01
02
03
04
05
06
07
08
09
10
var Employee = React.createClass({
  render: function() {
    return (
      <tr>
        <td>{this.props.employee.name}</td>
        <td>{this.props.employee.age}</td>
        <td>{this.props.employee.years}</td>
      </tr>);
  }
});

Вы можете добавить Bootstrap, чтобы стол выглядел красиво. Добавьте следующее чуть ниже тегов импорта скрипта:

Затем окружите основную таблицу контейнером div и присвойте элементу таблицы имена классов Bootstrap.

01
02
03
04
05
06
07
08
09
10
11
12
<div className="container">
  <table className="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Years</th>
      </tr>
    </thead>
    <tbody>{rows}</tbody>
  </table>
</div>

Обновление вашего браузера должно дать хороший обзор данных, которые вы жестко запрограммировали!

86263098

Добавление реальных данных

Чтобы использовать объекты данных, поступающие с сервера, вам необходимо добавить сервер! Делать это с Spring Boot очень просто. Внутри src/main/java/tutorial/Employee.java добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Data
@Entity
public class Employee {
  
    private @Id @GeneratedValue Long id;
    private String name;
    private int age;
    private int years;
  
    private Employee() {}
  
    public Employee(String name, int age, int years) {
        this.name = name;
        this.age = age;
        this.years = years;
    }
}

Это наш боб. Примечание: аннотация @Data из проекта Lombok .

Теперь создайте репозиторий, используя Spring Data JPA.

1
public interface EmployeeRepository extends CrudRepository<Employee, Long> {}

Чтобы загрузить данные, создайте реализацию CommandLineRunner которая использует репозиторий для создания новых записей в базе данных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@Component
public class DatabaseLoader implements CommandLineRunner {
  
    private final EmployeeRepository repository;
  
    @Autowired
    public DatabaseLoader(EmployeeRepository repository) {
        this.repository = repository;
    }
  
    @Override
    public void run(String... strings) throws Exception {
        this.repository.save(new Employee("Joe Biden", 45, 5));
        this.repository.save(new Employee("President Obama", 54, 8));
        this.repository.save(new Employee("Crystal Mac", 34, 12));
        this.repository.save(new Employee("James Henry", 33, 2));
    }
}

Единственное, что осталось — это вытащить зависимости. Добавление следующего в ваш pom.xml позволит вашему репозиторию стать конечной точкой REST.

1
2
3
4
5
6
7
8
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Вам также необходимо включить Project Lombok (который позволяет игнорировать создание методов получения и установки для ваших компонентов).

1
2
3
4
5
6
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
    <scope>provided</scope>
</dependency>

И вам нужна база данных (которую настраивает Spring Boot). Вы можете использовать H2, который встроен (то есть в память / не будет перезагружаться).

1
2
3
4
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

Вот и все! Если вы перезагрузитесь сейчас, у вас будет работающий REST-сервер с данными.

Сопоставление URL

Если вы добавите следующее в src/main/resources/application.properties тогда все ваши вызовы конечной точки REST будут на localhost:8080/api

1
spring.data.rest.basePath=/api

Вызов localhost:8080/api/employees из командной строки должен выдать список загруженных вами данных.

Реагировать и ОТДЫХАТЬ

Теперь вам нужно вытащить данные в представление React из конечной точки REST. Вы можете сделать это с помощью jQuery. Добавьте следующий импорт в ваш HTML:

Теперь создайте класс-оболочку, который возвращает EmployeeTable в своем методе render.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var App = React.createClass({
  
  loadEmployeesFromServer: function () {
    var self = this;
    $.ajax({
    }).then(function (data) {
      self.setState({employees: data._embedded.employees});
    });
  },
  
  getInitialState: function () {
    return {employees: []};
  },
  
  componentDidMount: function () {
    this.loadEmployeesFromServer();
  },
  
  render() {
    return ( <EmployeeTable employees={this.state.employees}/> );
  }
});

Вы должны сначала установить state , используя getInitialState для инициализации, а затем componentDidMount чтобы делать то, что нужно, когда все загружено.

Теперь замените основной ReactDOM.render вашим новым классом.

1
ReactDOM.render(<App />, document.getElementById('root') );

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

интерактивность

Последнее, что вы хотите для своего интерфейса, это интерактивность. Давайте добавим кнопку delete , чтобы увидеть, как это может работать.

Добавьте следующий столбец к вашему сотруднику визуализации.

1
2
3
<td>
    <button className="btn btn-info" onClick={this.handleDelete}>Delete</button>
</td>

Вы напишите метод handleDelete через секунду. После добавления еще одного заголовка в класс таблицы сотрудников рядом с каждой записью должны появиться кнопки.

8223957

Удаление с сервера

Прежде чем отправлять запросы на удаление в серверную часть, рекомендуется добавить уведомления. Для этого вы можете использовать Toastr, который позволит вам показывать всплывающие окна. Включите следующее в верхней части вашего HTML:

Теперь в вашем скрипте вы можете отправлять сообщения с такими командами, как toastr.error('something went wrong') .

Давайте проверим это! Измените свой класс сотрудника на следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var Employee = React.createClass({
  getInitialState: function() {
    return {display: true };
  },
  handleDelete() {
    var self = this;
    $.ajax({
      url: self.props.employee._links.self.href,
      type: 'DELETE',
      success: function(result) {
        self.setState({display: false});
      },
      error: function(xhr, ajaxOptions, thrownError) {
        toastr.error(xhr.responseJSON.message);
      }
    });
  },
  render: function() {
    if (this.state.display==false) return null;
    else return (
      <tr>
        <td>{this.props.employee.name}</td>
        <td>{this.props.employee.age}</td>
        <td>{this.props.employee.years}</td>
        <td>
          <button className="btn btn-info" onClick={this.handleDelete}>Delete</button>
        </td>
      </tr>
    );
  }
});

Это устанавливает состояние display которое определяет, следует ли отображать или нет. Если сотрудник успешно удален, эта переменная имеет значение true. Метод handleDelete отправляет запрос на удаление на сервер (используя ссылку, полученную из запроса get). В случае успеха display устанавливается на false, а рендер обновляется. В противном случае Toastr уведомляет пользователя о возникновении ошибки.

Попробуйте удалить запись и обновить страницу. Это должно остаться удаленным.

Примечание. Перезапуск сервера вернет те же данные, поскольку вы используете базу данных в памяти.

Добавить аутентификацию пользователя

Давайте добавим одну заключительную функцию в наше приложение React, Stormpath для аутентификации пользователей. Вам понадобится бесплатный аккаунт разработчика в Stormpath .

Первое, что вам нужно сделать, это поместить данные вашего приложения Stormpath в ваш файл application.properties .

1
2
3
stormpath.application.href = <your app href>
stormpath.client.apiKey.id = <your api key id>
stormpath.client.apiKey.secret = <your api key secret>

Примечание. Из соображений безопасности не следует хранить ключи Stormpath внутри файлов проекта. Скорее используйте переменные среды. Смотрите здесь

Затем добавьте стартер Stormpath в зависимости от Maven.

1
2
3
4
5
<dependency>
    <groupId>com.stormpath.spring</groupId>
    <artifactId>stormpath-default-spring-boot-starter</artifactId>
    <version>1.1.2</version>
</dependency>

Вам также необходимо переместить файл index.html в src/main/resources/templates Это связано с тем, что стартер Spring Boot в Stormpath по умолчанию использует библиотеку шаблонов Thymeleaf. Измените HomeController на возвращаемый index .

1
2
3
4
5
6
7
8
@Controller
public class HomeController {
  
    @RequestMapping(value = "/")
    public String index() {
        return "index";
    }
}

Вам также необходимо переместить код React в отдельный файл. Это потому, что Thymeleaf не понравятся некоторые персонажи. Переместите код из тега script в src/main/webapp/public/app.js Эта папка открыта для общего доступа по умолчанию. Затем импортируйте этот скрипт внизу вашего HTML.

1
<script type="text/babel" src="/public/app.js"></script>

Затем создайте адаптер безопасности, вызовы которого применяются к stormpath() .

1
2
3
4
5
6
7
@Configuration
public class Security extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.apply(stormpath());
    }
}

Теперь, когда вы перезагрузите свой сервер и попытаетесь перейти на домашнюю страницу, вам будет предложено ввести страницу входа.

3503857

При вводе данных, прилагаемых к приложению Stormpath, которое вы вводите в application.properties, вы должны перейти на страницу просмотра данных, как и раньше.

Выйти

Вы также должны быть в состоянии выйти из системы. Это так же просто, как добавить форму, которая отправляет сообщение в /logout (который Stormpath устанавливает по умолчанию).

1
2
3
4
5
6
<div class='container'>
    <div id='root'></div>
    <form action="/logout" method="post">
        <input class="btn btn-danger center-block" type="submit" value="Logout" />
    </form>
</div>

Вы можете окружить как корневой элемент React, так и форму контейнером Bootstrap для лучшего выравнивания.

3245088

Нажав кнопку выхода, вы вернетесь к экрану входа в систему, как и раньше.

Настройка авторизации

Наконец, вам нужно разрешить только пользователям с правильным доступом удалять сотрудников. Для блокировки вы можете использовать аннотацию PreAuthorize Spring Security. Измените код хранилища на следующий:

1
2
3
4
5
6
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
  
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @Override
    void delete(Long aLong);
}

Теперь только пользователи с полномочиями ROLE_ADMIN смогут удалять. Если вы перезагрузите сервер и попытаетесь нажать «Удалить», вы получите сообщение «Доступ запрещен».

36408723

Чтобы предоставить пользователю необходимые права, вам нужно добавить их в группу Stormpath через консоль администратора.

73134913-1024x423

В этом примере есть группа Supervisor, прикрепленная к соответствующему приложению. Для интеграции с этой группой вам просто нужно заменить строку ROLE_ADMIN на HREF группы и перезапустить. Если пользователь, вошедший в систему, является членом группы Supervisor (см. Учетные записи), вам следует разрешить удалить.

Сделано и вычищено

Точно так же вы создали совместимое веб-приложение CRUD с авторизацией и React в качестве внешнего интерфейса. Надеюсь, вы нашли этот урок полезным! Если у вас есть какие-либо вопросы по интеграции React, Spring Boot и Stormpath, пожалуйста, оставьте комментарий.

Чтобы увидеть более полное приложение React с использованием бэкэнда Spring Boot, см. React.js и Spring Data REST .

Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!