Статьи

OAuth с Spring Security

Из Википедии: OAuth ( открытая аутентификация ) — это открытый стандарт для аутентификации. Он позволяет пользователям делиться своими личными ресурсами (например, фотографиями, видео, списками контактов), хранящимися на одном сайте, с другим сайтом без необходимости выдавать свои учетные данные, обычно имя пользователя и пароль.

Есть много сообщений, рассказывающих об 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"
 clientId="foo" accessTokenUri="http://localhost:8080/cv/oauth/authorize"
 
 <beans:bean id="cvService" class="org.springsource.oauth.CVServiceImpl">
  <beans:property name="cvURL" value="http://localhost:8080/cv/cvs"></beans:property>
  <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