Статьи

5 практических советов по созданию вашего Spring Boot API

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

Каждый разработчик API ищет способы более безопасного управления своим приложением, не жертвуя скоростью или простотой внедрения новых функций. С этой целью мы недавно обновили основной продукт Stormpath – наш REST API – до Spring Boot. Попутно мы использовали ряд важных преимуществ, которые были бы полезны для любого, кто разрабатывает API с использованием Spring Boot.

Многим командам сложно управлять аутентификацией и контролем доступа к своим API, поэтому мы хотим поделиться некоторыми архитектурными принципами и советами из нашей миграции, чтобы упростить управление вашим Spring Boot API.

Примечание. Ниже мы используем инструмент командной строки httpie (https://github.com/jkbrzt/httpie) для изучения примеров.

1. Используйте аннотацию @RestController

Использование @RestController (вместо простого @Controller ) гарантирует, что вы @Controller объект Java, а не ссылку на шаблон HTML. Как это:

1
2
3
4
5
6
7
8
@RestController
public class HelloController {
  
    @RequestMapping("/")
    public String home() {
        return "hello";
    }
}

Выполнить: http -v localhost:8080

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
Date: Tue, 14 Jun 2016 23:55:16 GMT
Server: Apache-Coyote/1.1
  
hello

2. Воспользуйтесь преимуществами автоматического преобразования POJO в JSON

Spring Boot автоматически преобразует ваши POJO (старые классы Java) в JSON для вас!

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
@RestController
public class HelloController {
  
    @RequestMapping("/")
    public ApiResponse home() {
        return new ApiResponse("SUCCESS", "hello");
    }
}
  
public class ApiResponse {
  
    private String status;
    private String message;
  
    public ApiResponse(String status, String message) {
        this.status = status;
        this.message = message;
    }
  
    public String getStatus() {
        return status;
    }
  
    public String getMessage() {
        return message;
    }
}

Выполнить: http -v localhost:8080

01
02
03
04
05
06
07
08
09
10
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Tue, 14 Jun 2016 23:54:19 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
  
{
    "message": "hello",
    "status": "SUCCESS"
}

3. Используйте Внедрение Зависимостей С Автосервисными Услугами

Службы автопроводки позволяют абстрагировать бизнес-логику без сложной настройки, настройки или создания экземпляров объектов 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
@Service
public class HelloService {
  
    public String getGreeting(HttpServletRequest req) {
        String greeting = "World";
  
        Account account = AccountResolver.INSTANCE.getAccount(req);
        if (account != null) {
            greeting = account.getGivenName();
        }
  
        return greeting;
    }
}
  
@RestController
public class HelloController {
  
    @Autowired
    HelloService helloService;
  
    @RequestMapping("/")
    public ApiResponse home(HttpServletRequest req) {
        String greeting = helloService.getGreeting(req);
        return new ApiResponse("SUCCESS", "Hello " + greeting);
    }
}

В этом примере Stormpath возвращает персональное приветствие после проверки подлинности. Для этого вам сначала необходимо настроить учетную запись Stormpath, как описано здесь . Если вы следовали инструкциям и поместили файл ключа API Stormpath в стандартное расположение (~/.stormpath/apiKey.properties) , больше ничего не оставалось!

Запустите приложение и выполните это: http -v localhost:8080

01
02
03
04
05
06
07
08
09
10
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 00:56:46 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
  
{
    "message": "Hello World",
    "status": "SUCCESS"
}

Далее нам нужно пройти аутентификацию, чтобы мы могли двигаться дальше с нашим примером, поэтому мы будем использовать встроенную в Stormpath функциональность OAuth 2.0 для аутентификации и получения персонализированного сообщения. Убедитесь, что вы создали пользователя для своего приложения Stormpath в консоли администратора. Для получения дополнительной информации о поддержке OAuth Stormpath в Java SDK и его интеграции, ознакомьтесь с нашей документацией по продукту Java

1
2
3
4
5
http -v -f POST localhost:8080/oauth/token \
Origin:http://localhost:8080 \
grant_type=password \
username=<email address of the user you setup> \
password=<password of the user you setup>

Отклик:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Length: 938
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 00:59:43 GMT
Pragma: no-cache
Server: Apache-Coyote/1.1
  
{
    "access_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwic3R0IjoiYWNjZXNzIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiIzVFhQZ01Ld0NiQTk1VEp6VzBXTzRWIiwiaWF0IjoxNDY1OTUyMzgzLCJpc3MiOiJodHRwczovL2FwaS5zdG9ybXBhdGguY29tL3YxL2FwcGxpY2F0aW9ucy82dkZUNEFSZldDbXVIVlY4Vmt0alRvIiwic3ViIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hY2NvdW50cy8zcVlHbUl6VWh4UEtZTzI4a04wSWJSIiwiZXhwIjoxNDY1OTU1OTgzLCJydGkiOiIzVFhQZ0owckkwckFTZUU4SmtmN1NSIn0.o_pIHZVDZWogNuhJN2dmG4UKxACoWFxpRpp5OCyh6C4",
    "expires_in": 3600,
    "refresh_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwic3R0IjoicmVmcmVzaCIsImFsZyI6IkhTMjU2In0.eyJqdGkiOiIzVFhQZ0owckkwckFTZUU4SmtmN1NSIiwiaWF0IjoxNDY1OTUyMzgzLCJpc3MiOiJodHRwczovL2FwaS5zdG9ybXBhdGguY29tL3YxL2FwcGxpY2F0aW9ucy82dkZUNEFSZldDbXVIVlY4Vmt0alRvIiwic3ViIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hY2NvdW50cy8zcVlHbUl6VWh4UEtZTzI4a04wSWJSIiwiZXhwIjoxNDcxMTM2MzgzfQ.mJBfCgv4Sdnw7Ubzup7CZ1xdAIC9iO31AJE3NMmp05E",
    "token_type": "Bearer"
}

После этого сохраните токен доступа для использования с нашим приложением:

1
ACCESS_TOKEN=eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwic3R0IjoiYWNjZXNzIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiIzVFhQZ01Ld0NiQTk1VEp6VzBXTzRWIiwiaWF0IjoxNDY1OTUyMzgzLCJpc3MiOiJodHRwczovL2FwaS5zdG9ybXBhdGguY29tL3YxL2FwcGxpY2F0aW9ucy82dkZUNEFSZldDbXVIVlY4Vmt0alRvIiwic3ViIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hY2NvdW50cy8zcVlHbUl6VWh4UEtZTzI4a04wSWJSIiwiZXhwIjoxNDY1OTU1OTgzLCJydGkiOiIzVFhQZ0owckkwckFTZUU4SmtmN1NSIn0.o_pIHZVDZWogNuhJN2dmG4UKxACoWFxpRpp5OCyh6C4

Теперь давайте снова попробуем запустить наше приложение с аутентификацией:

01
02
03
04
05
06
07
08
09
10
11
12
http -v localhost:8080 Authorization:"Bearer $ACCESS_TOKEN"
  
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 01:05:35 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
  
{
    "message": "Hello Micah",
    "status": "SUCCESS"
}

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

4. Слой в Spring Security

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

Если вы хотите узнать больше, я также написал подробное руководство по Stormpath + Spring Security . В нашем проекте Java SDK с открытым исходным кодом у нас также есть отличное учебное пособие, которое поможет вам с нуля до полнофункционального приложения Spring Security + Spring Boot WebMVC . Найдите учебную документацию здесь .

По умолчанию все заблокировано в Spring Security, и интеграция Stormpath Spring Security является отличным примером, который следует этому соглашению. Чтобы испытать Spring Security с помощью Stormpath, вам просто нужно применить интеграцию Stormpath в такой конфигурации:

01
02
03
04
05
06
07
08
09
10
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.apply(stormpath()).and()
            .authorizeRequests()
            .antMatchers("/").permitAll();
    }
}

http.apply(stormpath()) – это все, что нужно для настройки интеграции Stormpath Spring Security. Следующие две строки разрешают неаутентифицированный доступ к конечной точке “/” .

Давайте посмотрим, как это влияет на метод в нашем контроллере:

1
2
3
4
5
6
7
8
@RequestMapping("/restricted")
public ApiResponse restricted(HttpServletRequest req) {
    // guaranteed to have account because of Spring Security
    return new ApiResponse(
        "SUCCESS",
        "Hello " + AccountResolver.INSTANCE.getAccount(req).getGivenName()
    );
}

В этом случае нет необходимости выполнять нулевую проверку учетной записи, поскольку мы знаем, что единственный способ попасть в этот метод, если после проверки подлинности. Например:

1
2
3
4
5
6
7
8
http localhost:8080/restricted
  
HTTP/1.1 302 Found
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Wed, 15 Jun 2016 17:32:31 GMT
Expires: 0
Location: http://localhost:8080/login

Мы перенаправлены на / логин, так как мы не аутентифицированы. Если я использую свой токен доступа, как и раньше, он выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
http localhost:8080/restricted Authorization:"Bearer $ACCESS_TOKEN"
  
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 17:34:34 GMT
Expires: 0
Pragma: no-cache
  
{
    "message": "Hello Micah",
    "status": "SUCCESS"
}

5. Единая обработка ошибок

Хороший дизайн API требует, чтобы ваш API возвращал общий ответ, даже если что-то идет не так. Это делает анализ и маршалинг JSON в объекты Java проще и надежнее.

Давайте попробуем пример. Здесь нам нужен заголовок под названием: Custom-Header . Если этот заголовок отсутствует, генерируется исключение:

01
02
03
04
05
06
07
08
09
10
11
@RequestMapping("/custom-header")
public ApiResponse customHeader(HttpServletRequest req) throws MissingCustomHeaderException {
    String customHeader = req.getHeader("Custom-Header");
    if (customHeader == null) {
        throw new MissingCustomHeaderException(
            "'Custom-Header' on the request is required."
        );
    }
  
    return new ApiResponse("SUCCESS", "Found Custom-Header: " + customHeader);
}

Если мы посмотрим на «счастливый путь», все хорошо

01
02
03
04
05
06
07
08
09
10
11
12
13
http localhost:8080/custom-header \
    Custom-Header:MyCustomValue \
    Authorization:"Bearer $ACCESS_TOKEN"
  
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 22:28:47 GMT
  
{
    "message": "Found Custom-Header: MyCustomValue",
    "status": "SUCCESS"
}

Что если у нас нет Custom-Header ?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
http localhost:8080/custom-header Authorization:"Bearer $ACCESS_TOKEN"
  
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 22:34:13 GMT
  
{
    "error": "Internal Server Error",
    "exception": "com.stormpath.spring.boot.examples.controller.HelloController$MissingCustomHeaderException",
    "message": "'Custom-Header' on the request is required.",
    "path": "/custom-header",
    "status": 500,
    "timestamp": 1466030053360
}

Итак, что с этим не так? С одной стороны, это не соответствует формату ответа, который мы уже установили. Кроме того, это приводит к 500 (Internal Server Error) , которая никогда не бывает хорошей.

К счастью, Spring Boot позволяет легко это исправить. Все, что нам нужно сделать, это добавить обработчик исключений. Никаких других изменений кода не требуется.

1
2
3
4
5
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingCustomHeaderException.class)
public ApiResponse exception(MissingCustomHeaderException e) {
    return new ApiResponse("ERROR", e.getMessage());
}

Давайте посмотрим на ответ сейчас:

01
02
03
04
05
06
07
08
09
10
11
12
http localhost:8080/custom-header Authorization:"Bearer $ACCESS_TOKEN"
  
HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Wed, 15 Jun 2016 22:59:32 GMT
  
{
    "message": "'Custom-Header' on the request is required.",
    "status": "ERROR"
}

Теперь у нас правильный ответ 400 (Bad Request) . У нас также есть ответ в том же формате, что и успешные ответы.

Бонусный совет: попробуйте Stormpath

Stormpath предлагает расширенный, ориентированный на разработчиков сервис идентификации, который включает в себя как аутентификацию, так и авторизацию и может быть реализован за считанные минуты. API-интерфейс Stormpath REST позволяет разработчикам быстро и легко создавать широкий спектр функций управления пользователями, которые в противном случае им пришлось бы кодировать самостоятельно, включая:

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

Кнопка-знак вверх, теперь