Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте 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 позволяет разработчикам быстро и легко создавать широкий спектр функций управления пользователями, которые в противном случае им пришлось бы кодировать самостоятельно, включая:
- Сложная поддержка авторизации с кэшированием для максимальной производительности
- Аутентификация и отзыв токена с помощью веб-токенов JSON и OAuth2
- Встроенная поддержка мультитенантных приложений с предварительно созданным разделением данных клиентов.
- Полная документация и забота о клиентах — даже для бесплатных аккаунтов разработчиков
- Надежные и идиоматические SDK
Управление идентификацией здания, включая аутентификацию и авторизацию? Попробуйте Stormpath! Наш REST API и надежная поддержка Java SDK могут устранить риск для безопасности и могут быть реализованы за считанные минуты. Зарегистрируйтесь и никогда больше не создавайте аутентификацию!