Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте 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
|
@RestControllerpublic class HelloController { @RequestMapping("/") public String home() { return "hello"; }} |
Выполнить: http -v localhost:8080
|
1
2
3
4
5
6
7
|
HTTP/1.1 200 OKContent-Length: 5Content-Type: text/plain;charset=UTF-8Date: Tue, 14 Jun 2016 23:55:16 GMTServer: 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
|
@RestControllerpublic 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 OKContent-Type: application/json;charset=UTF-8Date: Tue, 14 Jun 2016 23:54:19 GMTServer: Apache-Coyote/1.1Transfer-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
|
@Servicepublic class HelloService { public String getGreeting(HttpServletRequest req) { String greeting = "World"; Account account = AccountResolver.INSTANCE.getAccount(req); if (account != null) { greeting = account.getGivenName(); } return greeting; }} @RestControllerpublic 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 OKContent-Type: application/json;charset=UTF-8Date: Wed, 15 Jun 2016 00:56:46 GMTServer: Apache-Coyote/1.1Transfer-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 OKCache-Control: no-storeContent-Length: 938Content-Type: application/json;charset=UTF-8Date: Wed, 15 Jun 2016 00:59:43 GMTPragma: no-cacheServer: 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 OKContent-Type: application/json;charset=UTF-8Date: Wed, 15 Jun 2016 01:05:35 GMTServer: Apache-Coyote/1.1Transfer-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
|
@Configurationpublic 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 FoundCache-Control: no-cache, no-store, max-age=0, must-revalidateContent-Length: 0Date: Wed, 15 Jun 2016 17:32:31 GMTExpires: 0Location: 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 OKCache-Control: no-cache, no-store, max-age=0, must-revalidateContent-Type: application/json;charset=UTF-8Date: Wed, 15 Jun 2016 17:34:34 GMTExpires: 0Pragma: 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 OKCache-Control: no-cache, no-store, max-age=0, must-revalidateContent-Type: application/json;charset=UTF-8Date: 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 ErrorCache-Control: no-cache, no-store, max-age=0, must-revalidateConnection: closeContent-Type: application/json;charset=UTF-8Date: 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 RequestCache-Control: no-cache, no-store, max-age=0, must-revalidateConnection: closeContent-Type: application/json;charset=UTF-8Date: 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 позволяет разработчикам быстро и легко создавать широкий спектр функций управления пользователями, которые в противном случае им пришлось бы кодировать самостоятельно, включая:
- Сложная поддержка авторизации с кэшированием для максимальной производительности
- Аутентификация и отзыв токена с помощью веб-токенов JSON и OAuth2
- Встроенная поддержка мультитенантных приложений с предварительно созданным разделением данных клиентов.
- Полная документация и забота о клиентах — даже для бесплатных аккаунтов разработчиков
- Надежные и идиоматические SDK
Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!
