Как вы все знаете, Spring Boot — это инструментарий, который позволяет очень просто быстро разрабатывать мощные веб-сервисы. Он очень модульный и хорошо сочетается с другими фреймворками и инструментами. В этом уроке я собираюсь показать вам, как легко настроить RESTful API для существующей базы данных SQL, используя Speedment в качестве ORM.
Фон
Speedment — это набор инструментов с открытым исходным кодом, который подключается к базе данных, анализирует метаданные и использует их для создания классов сущностей и менеджеров для моделирования базы данных объектно-ориентированным способом. Он также содержит библиотеку времени выполнения, которая превращает потоки Java 8 в оптимизированные запросы SQL, что позволяет писать код очень безопасно и современно. Как оказалось, это идеально подходит для Spring Application.
Цель этого урока
Цель этого руководства — разработать REST API с помощью Spring, который предоставляет доступ к различным конечным точкам в зависимости от роли пользователя. Если вы хотите заглянуть в будущее, вы можете найти все источники из руководства здесь .
| POST / аккаунт | Доступно любому |
| GET / account / {id} | Доступно как для администраторов, так и для пользователей |
| GET / аккаунт | Перечисляет все учетные записи и доступен только администраторам |
Аутентификация будет производиться с использованием базы данных MySQL, которую мы будем запрашивать с использованием стандартных потоков Java 8. В итоге у нас будет полностью объектно-ориентированное решение, готовое к расширению с помощью специальной бизнес-логики!
Если вы хотите следовать готовому проекту, вы можете клонировать его на GitHub.
Шаг 1: База данных
Для простоты я использую пользовательскую базу данных MySQL, которую я создал с помощью следующего оператора. Однако идея заключается в том, что вы можете использовать любую существующую базу данных, к которой у вас есть доступ.
|
1
2
3
4
5
6
7
8
9
|
create database `securerest`;use `securerest`;create table `account` ( `id` bigint not null auto_increment primary key, `username` varchar(30) not null unique, `password` char(60) not null, `role` enum('USER', 'ADMIN') not null); |
Шаг 2. Создание проекта Maven
Чтобы быстро запустить новый Spring Project, я рекомендую отличный сайт Spring Initializr . Там вы можете легко ввести зависимости, которые вам нужны для вашего проекта.
Нам нужны следующие зависимости Spring:
- весна-загрузка-стартер-безопасность
- весна-загрузка-стартер-веб
- MySQL-разъем-Java
Во-вторых, нам также нужно добавить зависимость Speedment и плагин Speedment для генерации нашего кода.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<dependency> <groupId>com.speedment</groupId> <artifactId>runtime</artifactId> <version>${speedment.version}</version> <type>pom</type></dependency>...<plugin> <groupId>com.speedment</groupId> <artifactId>speedment-maven-plugin</artifactId> <version>${speedment.version}</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> <scope>runtime</scope> </dependency> </dependencies></plugin> |
Шаг 3: Создать модель домена
С добавленным плагином мы можем запустить следующую цель Maven, чтобы запустить интерфейс Speedment.
|
1
|
mvn speedment:tool |
Это откроет инструмент ускорения. Он попросит вас ввести имя пользователя, пароль, имя схемы и т. Д. Для вашей базы данных. Введите значения для полей и нажмите «Подключиться».
Внутри пользовательского интерфейса есть много конфигураций, которые вы можете сделать. Вы можете указать точное местоположение сгенерированного кода, отключить таблицы или столбцы, которые не имеют отношения к приложению, и создать псевдонимы, если вы хотите, чтобы поле или таблица отображались как-то еще в коде.
Для этого урока достаточно нажать «Создать». Теперь вы должны увидеть, как несколько новых классов и пакетов Java генерируются в вашем проекте!
Шаг 4. Настройка Spring Security
Чтобы Spring мог запускать Speedment как Spring Bean, нам нужно указать, как создается экземпляр. Для этого мы создаем класс с именем SpeedmentConfiguration.
SpeedmentConfiguration.java
|
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
|
@Configurationpublic class SpeedmentConfiguration { private @Value("${dbms.host}") String host; private @Value("${dbms.port}") int port; private @Value("${dbms.schema}") String schema; private @Value("${dbms.username}") String username; private @Value("${dbms.password}") String password; @Bean public SpeedmentApplication getSpeedmentApplication() { return new SpeedmentApplicationBuilder() .withIpAddress(host) .withPort(port) .withUsername(username) .withPassword(password) .withSchema(schema) .build(); } @Bean public AccountManager getAccountManager(SpeedmentApplication app) { return app.getOrThrow(AccountManager.class); }} |
Поля @Value в верхней части загружаются по умолчанию из файла с именем application.properties. Поэтому мы должны указать значения там:
application.properties
|
1
2
3
4
5
6
7
8
9
|
# Speedment Settingsdbms.host=localhostdbms.port=3306dbms.schema=securerestdbms.username=rootdbms.password=password# Server Settingsserver.port=9777 |
Далее нам нужно создать класс конфигурации для аутентификации пользователей. Мы называем этот класс AuthenticationConfiguration.
AuthenticationConfiguration.java
|
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
32
33
34
35
36
37
38
|
@Configurationpublic class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { private @Autowired AccountManager accounts; @Bean public DaoAuthenticationProvider authProvider() { final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(getUserDetailsService()); authProvider.setPasswordEncoder(getPasswordEncoder()); return authProvider; } @Bean public UserDetailsService getUserDetailsService() { return username -> accounts.stream() .filter(Account.USERNAME.equal(username)) .findAny() .orElseThrow(() -> new UsernameNotFoundException( "Could not find the user '" + username + "'" )); } @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override public void init( AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(getUserDetailsService()) .passwordEncoder(getPasswordEncoder()); }} |
Обратите внимание, как мы можем использовать @Autowired в сгенерированном AccountManager, поскольку он указан как Spring Bean в классе SpeedmentConfiguration.
В методе getUserDetailsService () мы используем Stream API с пользовательским предикатом, что позволяет превратить поток в оптимизированный SQL-запрос. Однако есть одна вещь, которую нам нужно сделать, чтобы этот метод работал. Нам нужно убедиться, что созданный интерфейс учетной записи расширяет UserDetails, чтобы его можно было беспрепятственно использовать с Spring API. Это легко сделать, так как Speedment не перезаписывает файлы, которые не начинаются с префикса «Сгенерировано».
Account.java
|
1
2
3
|
public interface Account extends GeneratedAccount, UserDetails { } |
Нам также нужно добавить несколько методов в класс реализации для поддержки интерфейса.
AccountImpl.java
|
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
|
@JsonIgnoreProperties("password")public final class AccountImpl extends GeneratedAccountImpl implements Account { private static final long serialVersionUID = -7552975849070084309L; @Override @JsonIgnore public Collection<? extends GrantedAuthority> getAuthorities() { return createAuthorityList(getRole()); } @Override @JsonIgnore public boolean isAccountNonExpired() { return true; } @Override @JsonIgnore public boolean isAccountNonLocked() { return true; } @Override @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @Override @JsonIgnore public boolean isEnabled() { return true; }} |
Затем мы создаем третий файл конфигурации, чтобы определить, какие права требуются для доступа к различным конечным точкам REST.
SecurityConfiguration.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@Configuration@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(POST, "/account").permitAll() .antMatchers(GET, "/account").hasAuthority("ROLE_ADMIN") .anyRequest().fullyAuthenticated() .and().httpBasic() .and().csrf().disable(); }} |
Шаг 5: Создайте контроллер
Последний шаг — создать класс Controller, в котором находится наша бизнес-логика. Класс аннотируется с помощью @RestController, чтобы Spring мог автоматически его подбирать. Он определяет три отображения, по одному для каждой конечной точки REST.
AccountController.java
|
1
2
3
4
5
6
7
8
9
|
@RestControllerpublic class AccountController { private @Autowired AccountManager accounts; private @Autowired PasswordEncoder passwordEncoder; ...} |
Первая конечная точка — команда регистрации. Он будет расположен на «POST / account». Мы берем два параметра: имя пользователя и пароль, хешируем пароль и затем сохраняем его в базе данных. Операция завершится ошибкой, если имя пользователя уже существует, поскольку оно определено в базе данных как UNIQUE.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@PostMapping("/account") long onPostAccount( @RequestParam("username") String username, @RequestParam("password") String password) { final Account created = accounts.persist(new AccountImpl() .setUsername(username) .setPassword(passwordEncoder.encode(password)) .setRole("USER") ); return created.getId(); } |
Далее следует конечная точка «GET / account». Это довольно просто. Мы инициируем поток, используя сгенерированный класс менеджера. Затем поток оптимизируется в SQL-запрос с помощью Speedment.
|
1
2
3
4
|
@GetMapping("/account") List<Account> onGetAllAccounts() { return accounts.stream().collect(toList()); } |
Последняя конечная точка немного сложнее. Мы настроили Spring, чтобы разрешить только зарегистрированным пользователям доступ к «GET / account {id}», но мы хотим, чтобы пользователи могли получать доступ только к своей собственной информации, если у них нет роли «ADMIN».
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
@GetMapping("/account/{id}") Account onGetAccount( @PathVariable("id") long accountId, Authentication auth) { final Account account = (Account) auth.getPrincipal(); if (account.getId() == accountId) { return account; } else if ("ADMIN".equals(account.getRole())) { return accounts.stream() .filter(Account.ID.equal(accountId)) .findAny().orElseThrow(NotFoundException::new); } else { throw new ForbiddenException(); } } |
Готово! Теперь у нас есть REST API, который использует базу данных для хранения пользователей и базовую аутентификацию, чтобы пользователи могли вызывать только те команды, к которым у них есть доступ!
Пробовать
Чтобы попробовать REST API, который мы только что создали, просто запустите терминал и используйте команду cURL!
Чтобы зарегистрировать аккаунт:
|
1
2
3
|
curl -X POST "http://localhost:9777/account ?username=my_user &password=my_pass" |
Чтобы увидеть нашу собственную информацию (в данном случае мы пользователь с ID 1):
|
1
|
curl -X GET -u my_user:my_pass "http://localhost:9777/account/1" |
Чтобы вывести список всех пользователей (требуется роль ADMIN):
|
1
|
curl -X GET -u my_user:my_pass "http://localhost:9777/account" |
Резюме
В этом руководстве мы создали новый проект Spring Boot, чтобы быстро сопоставить RESTful API с простой системой регистрации и использовать Speedment для создания объектно-ориентированного уровня доступа к базе данных для этого API. Мы также настроили Spring Security так, чтобы пользователи могли сами проходить аутентификацию для доступа к определенным конечным точкам.
Дополнительные руководства и учебные пособия по использованию Speedment можно найти на странице GitHub ! Там вы также можете найти несколько примеров проектов о том, как использовать больше интересных функций генерации кода, доступных в Speedment!
Полные источники для этого урока можно найти здесь !
До скорого!
| Ссылка: | Как настроить безопасный REST API с помощью Spring от нашего партнера по JCG Эмиля Форслунда из блога Age of Java . |

