Есть много сообщений, рассказывающих об OAuth со стороны клиента , например, о том, как подключиться к поставщикам услуг, таких как Twitter или Facebook, но меньше сообщений об OAuth, но со стороны сервера , более конкретно, как реализовать механизм аутентификации с использованием OAuth для защиты ресурсы, а не для доступа к ним ( часть на стороне клиента ).
В этом посте я расскажу о том, как защитить свои ресурсы, используя Spring Security ( Spring Security OAuth ). Пример будет достаточно простым, чтобы понять основы реализации поставщика услуг OAuth .
Я нашел этот пост, который объясняет на простом примере, что такое OAuth и как он работает. Я думаю, что это хорошая отправная точка с OAuth http://hueniverse.com/2007/10/beginners-guide-to-oauth-part-ii-protocol-workflow/
Теперь пришло время начать писать наш поставщик услуг. Прежде всего я объясню, что предложит наш поставщик услуг .
Представьте, что вы разрабатываете веб-сайт (называемый CV ), где пользователи будут регистрироваться, и после этого они смогут загружать свои биографические данные . Теперь мы собираемся преобразовать этот веб-сайт в поставщика услуг, где OAuth будет использоваться для защиты ресурсов (биографические данные зарегистрированных пользователей). Представьте себе снова, что некоторые компании согласились с сотрудниками CV, что, когда они публикуют вакансии, пользователи будут иметь возможность загружать свои учебные программы непосредственно с сайта CV в отдел кадров вместо отправки по электронной почте или копирования и вставки из документа. Как вы можете видеть здесь, OAuth начинает управлять безопасностью между веб-сайтом CV и сайтом компании RH .
Таким образом, у нас есть поставщик биографических данных ( CV ) с защищенным ресурсом (сам документ). Потребителями являются компании, которые предлагают пользователям возможность напрямую получать свои биографические данные из резюме . Поэтому, когда пользователь посещает вакансии в компании (в нашем примере это называется fooCompany ) и хочет подать заявку на работу, ему нужно только авторизовать веб-сайт FooCompany «Вакансии» с разрешениями на загрузку его резюме с CV- сайта.
Поскольку мы будем использовать Spring Security для проверки подлинности OAuth , в первую очередь мы собираемся настроить Spring Security в приложении SpringMVC CV . Здесь ничего особенного:
В файле web.xml мы определяем фильтр безопасности :
1
2
3
4
5
6
7
8
9
|
< filter > < filter-name >springSecurityFilterChain</ filter-name > < filter-class >org.springframework.web.filter.DelegatingFilterProxy</ filter-class > </ filter > < filter-mapping > < filter-name >springSecurityFilterChain</ filter-name > < url-pattern >/*</ url-pattern > </ filter-mapping > |
А в root-context.xml мы определяем защищенные ресурсы и менеджер аутентификации. В этом случае в памяти apporoach используется:
01
02
03
04
05
06
07
08
09
10
11
|
< http auto-config = 'true' > < intercept-url pattern = "/**" access = "ROLE_USER" /> </ http > < authentication-manager > < authentication-provider > < user-service > < user name = "leonard" password = "nimoy" authorities = "ROLE_USER" /> </ user-service > </ authentication-provider > </ authentication-manager > |
Следующим шагом создайте Spring Controller, который возвращает биографические данные зарегистрированного пользователя:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
@RequestMapping (value= "/cvs" , method=RequestMethod.GET) @ResponseBody public String loadCV() { StringBuilder cv = new StringBuilder(); cv.append( "Curriculum Vitae -- Name: " ).append(getUserName()).append( " Experience: Java, Spring Security, ..." ); return cv.toString(); } private String getUserName() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username; if (principal instanceof UserDetails) { username = ((UserDetails)principal).getUsername(); } else { username = principal.toString(); } return username; } |
Этот контроллер возвращает непосредственно String, а не объект ModelView . Эта строка отправляется непосредственно как HttpServletResponse .
Теперь у нас есть простой веб-сайт, который возвращает биографические данные зарегистрированного пользователя. Если вы попытаетесь получить доступ к ресурсу / cvs , если вы не прошли аутентификацию, Spring Security покажет вам страницу входа, и, если вы уже вошли в систему, ваш опыт работы будет возвращен. Работает как любой другой сайт, который использует Spring Security.
Следующим шагом является изменение этого проекта, чтобы внешние сайты могли получать доступ к защищенным ресурсам, используя протокол аутентификации OAuth 2 .
В root-context.xml:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
< beans:bean id = "tokenServices" class = "org.springframework.security.oauth2.provider.token.InMemoryOAuth2ProviderTokenServices" > < beans:property name = "supportRefreshToken" value = "true" /> </ beans:bean > < oauth:provider client-details-service-ref = "clientDetails" token-services-ref = "tokenServices" > < oauth:verification-code user-approval-page = "/oauth/confirm_access" /> </ oauth:provider > < oauth:client-details-service id = "clientDetails" > < oauth:client clientId = "foo" authorizedGrantTypes = "authorization_code" /> </ oauth:client-details-service > |
Первый компонент — реализация интерфейса OAuth2ProviderTokenServices с идентификатором tokenServices. Интерфейс OAuth2ProviderTokenServices определяет операции, необходимые для управления токенами OAuth 2.0 . Эти токены должны быть сохранены для последующего доступа токен может ссылаться на него. Для этого примера достаточно магазина InMemory.
Следующий компонент — <oauth: provider>. Этот тег используется для настройки механизма поставщика OAuth 2.0. И в этом случае три параметра настроены; Первый — это ссылка на bean-компонент, который определяет службу подробностей клиента, объясненную в следующем параграфе. Второй — сервис токенов для предоставления токенов, описанный в предыдущем параграфе, а последний — URL-адрес, по которому будет обрабатываться запрос на авторизацию токена. Обычно это страница авторизации / запрета, на которой поставщик услуг запрашивает у пользователя, разрешает ли он (в нашем случае fooCompany ) доступ к защищенным ресурсам (его резюме ).
Последний компонент — <oauth: client-details-service>. В этом теге вы определяете, каким клиентам вы разрешаете доступ к защищенным ресурсам с предыдущей аутентификацией. В этом случае, поскольку CV- компания договорилась с foo о том, что они могут подключиться к ее службе Curriculum Vitae Service, клиент определяется с помощью id foo .
Теперь у нас есть приложение, настроенное с помощью OAuth . Последний шаг — создание контроллера для приема запросов с URL-адреса / oauth / verify_access .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private ClientAuthenticationCache authenticationCache = new DefaultClientAuthenticationCache(); private ClientDetailsService clientDetailsService; @RequestMapping (value= "/oauth/confirm_access" ) public ModelAndView accessConfirmation(HttpServletRequest request, HttpServletResponse response) { ClientAuthenticationToken clientAuth = getAuthenticationCache().getAuthentication(request, response); if (clientAuth == null ) { throw new IllegalStateException( "No client authentication request to authorize." ); } ClientDetails client = getClientDetailsService().loadClientByClientId(clientAuth.getClientId()); TreeMap<String, Object> model = new TreeMap<String, Object>(); model.put( "auth_request" , clientAuth); model.put( "client" , client); return new ModelAndView( "access_confirmation" , model); } |
Этот контроллер возвращает объект ModelAndView с информацией о клиенте и какую страницу следует показать для предоставления разрешения защищенным ресурсам. Эта страница JSP называется access_confirmation.jsp, и самая важная часть:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<div id= "content" > <% if (session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null && !(session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) instanceof UnapprovedClientAuthenticationException)) { %> <div class = "error" > <p>Access could not be granted. (<%= ((AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage() %>)</p> </div> <% } %> <c:remove scope= "session" var= "SPRING_SECURITY_LAST_EXCEPTION" /> <authz:authorize ifAllGranted= "ROLE_USER" > <h2>Please Confirm</h2> <p>You hereby authorize <c:out value= "${client.clientId}" /> to access your protected resources.</p> <form id= "confirmationForm" name= "confirmationForm" action= "<%=request.getContextPath() + VerificationCodeFilter.DEFAULT_PROCESSING_URL%>" method= "post" > <input name= "<%=BasicUserApprovalFilter.DEFAULT_APPROVAL_REQUEST_PARAMETER%>" value= "<%=BasicUserApprovalFilter.DEFAULT_APPROVAL_PARAMETER_VALUE%>" type= "hidden" /> <label><input name= "authorize" value= "Authorize" type= "submit" /></label> </form> <form id= "denialForm" name= "denialForm" action= "<%=request.getContextPath() + VerificationCodeFilter.DEFAULT_PROCESSING_URL%>" method= "post" > <input name= "<%=BasicUserApprovalFilter.DEFAULT_APPROVAL_REQUEST_PARAMETER%>" value= "not_<%=BasicUserApprovalFilter.DEFAULT_APPROVAL_PARAMETER_VALUE%>" type= "hidden" /> <label><input name= "deny" value= "Deny" type= "submit" /></label> </form> </authz:authorize> </div> |
Как вы видите, Spring Security OAuth предоставляет вспомогательные классы для создания формы подтверждения и формы отказа. Когда результат передается, вызывается URL / cv / oauth / user / authorize (с внутренним управлением), там OAuth решает, возвращает ли защищенный ресурс (String, возвращенный методом loadCV ()) вызывающей стороне или нет, в зависимости от того, какую опцию выбрал пользователь.
И это все о создании системы OAuth 2 с использованием Spring Security OAuth . Но я полагаю, вы задаетесь вопросом, как его протестировать, поэтому за ту же цену я объясню, как написать клиентскую часть (Consumer), используя Spring Security OAuth .
Клиентское приложение (называемое fooCompany ) также является веб-приложением SpringMVC с Spring Security .
Spring Security часть здесь будет игнорироваться.
Клиентское приложение содержит домашнюю страницу ( home.jsp ) со ссылкой на Spring Controller, которая будет отвечать за загрузку Curriculum Vitae с сайта CV и перенаправление контента в представление ( show.jsp ).
1
2
3
4
5
6
7
8
9
|
@RequestMapping (value= "/cv" ) public ModelAndView getCV() { String cv = cvService.getCVContent(); Map<String, String> params = new HashMap<String, String>(); params.put( "cv" , cv); ModelAndView modelAndView = new ModelAndView( "show" , params); return modelAndView; } |
Как видите, это простой контроллер, который вызывает сервис Curriculum Vitae . Эта служба будет отвечать за подключение к веб-сайту CV и загрузку необходимой биографии . Конечно, это касается и протокола связи OAuth .
Сервис выглядит:
1
2
3
4
|
public String getCVContent() { byte [] content = (getCvRestTemplate().getForObject(URI.create(cvURL), byte []. class )); return new String(content); } |
Предлагаемый способ доступа к этим ресурсам — использование Rest. Для этой цели Spring Security OAuth предоставляет расширение RestTemplate для работы с протоколом OAuth . Этот класс ( OAuth2RestTemplate ) управляет подключением к необходимым ресурсам, а также управляет токенами, протоколом авторизации OAuth ,…
OAuth2RestTemplate внедряется в CVService и настраивается в root-context.xml:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
< oauth:client token-services-ref = "oauth2TokenServices" /> < beans:bean id = "oauth2TokenServices" class = "org.springframework.security.oauth2.consumer.token.InMemoryOAuth2ClientTokenServices" /> < oauth:resource id = "cv" type = "authorization_code" < beans:bean id = "cvService" class = "org.springsource.oauth.CVServiceImpl" > < beans:property name = "cvRestTemplate" > < beans:bean class = "org.springframework.security.oauth2.consumer.OAuth2RestTemplate" > < beans:constructor-arg ref = "cv" /> </ beans:bean > </ beans:property > < beans:property name = "tokenServices" ref = "oauth2TokenServices" ></ beans:property > </ beans:bean > |
Обратите внимание, что OAuth2RestTemplate создается с использованием ресурса OAuth, который содержит всю информацию о том, куда подключаться для авторизации доступа к защищенному ресурсу, и в данном случае это веб-сайт CV. Обратите внимание, что мы ссылаемся на внешний веб-сайт, хотя в этом примере мы используем localhost. Также задан URL-адрес поставщика услуг (http: // localhost: 8080 / cvs / cv), поэтому RestTemplate может установить соединение с поставщиком содержимого, а в случае успешного завершения процесса авторизации получить запрошенную информацию.
<oauth: resource> определяет ресурсы OAuth , в данном случае имя клиента (помните, что это значение было настроено в теге сведений о клиенте на стороне сервера для предоставления доступа к протоколу OAuth ). Также определяется userAuthorizationUri . Это URI, на который пользователь будет перенаправлен, если ему когда-либо понадобится авторизовать доступ к ресурсу (это внутренний URI, управляемый Spring Security OAuth ). И, наконец, accessTokenUri , конечная точка поставщика URI OAuth, которая предоставляет маркер доступа (также внутренний URI).
Также создание потребителя с помощью Spring Security OAuth достаточно просто.
Теперь я объясню последовательность событий, которые происходят, когда пользователь хочет предоставить компании foo доступ к ее биографическим данным.
Прежде всего, пользователь подключается к веб-сайту foo и щелкает ссылку « Биографическая справка» . Затем вызывается метод getCV из контроллера. Этот метод вызывает cvService , который в то же время создает соединение с ресурсом URI (CV), используя OAuth2RestTemplate . И этот класс действует как черный ящик со стороны клиента, вы точно не знаете, что будет делать этот класс, но он возвращает ваше резюме, хранящееся на сайте CV . Как вы можете себе представить, этот класс управляет всеми рабочими процессами, связанными с OAuth , такими как управление токенами, выполнение необходимых перенаправлений URL-адресов для получения разрешений… и, если все шаги выполнены успешно, сохраненные биографические данные на сайте CV будут отправлены на сайт компании foo .
И это все шаги, необходимые для того, чтобы ваш сайт мог выступать в качестве поставщика услуг, используя протокол авторизации OAuth2 . Спасибо сотрудникам Spring Security , на первый взгляд гораздо проще.
Надеюсь, что вы найдете ее полезной.
Скачать ServerSide (CV)
Скачать ClientSide (fooCompany)
Ссылка: OAuth с Spring Security от нашего партнера JCG