Статьи

Как настроить безопасный REST API с помощью Spring

Как вы все знаете, 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
@Configuration
public 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 Settings
dbms.host=localhost
dbms.port=3306
dbms.schema=securerest
dbms.username=root
dbms.password=password
 
# Server Settings
server.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
@Configuration
public 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
@EnableWebSecurity
public 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
@RestController
public 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 .