Java rest api authorization

6 способов: как добавить security для Rest сервиса в Java

В данной статье я попытаюсь описать несколько способов, а точнее 6, как добавить security для rest сервиса на Java.

Перед нашей командой была поставлена задача найти все возможные способы добавить security к rest сервису. Проанализировать все за и против и выбрать наиболее подходящий для нашего проекта. Когда я начал искать такую статью в Гугле ничего подходящего не нашел, а были лишь фрагменты и мне пришлось собирать эту информацию по крупицам. Так что думаю, данная статья будет полезна и другим Java разработчикам, пишущим back-end. Я не буду утверждать, что какой-то из этих способов лучше или хуже, все зависит от поставленной задачи и конкретного проекта. Поэтому какой из шести способов подходит больше всего вашему проекту решать только Вам. Я постараюсь описать принцип каждого из подходов и дать небольшой пример с использованием Java и Spring Security.

Способ первый: Basic Authentication

Basic Authentication — юзер или рест клиент указывает свой логин и пароль для получения доступа к рест сервису. Логин и пароль передаются по сети как незашифрованный текст кодированный простым Base64 и может быть легко декодирован любым пользователем. При использовании такого метода, обязательно должен использоваться https протокол для передачи данных.

Конфигурация очень простая, так будет выглядеть security.xml для нашего Spring Security

@RequestMapping("/rest/api") @RestController public class RestController < @RequestMapping public Object getInfo() < return //some response MyClass; >> 

И наконец рест-клиент на базе спринового RestTemplate. В хидер добавляем слово Basic пробел потом логин и пароль без пробелов, разделенный двоеточием и закодированный Base64.

 RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/rest/api"; HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Basic QWxhZGRpbupvcRVuIHNlc2FtZQ= https://github.com/eugenp/tutorials/tree/master/spring-security-rest-digest-auth" rel="nofollow">такого клиента. 

Способ третий: Token Authentication


Суть этого способа заключается в том, что пользователь используя свои креденшелы логинится в приложение и получает токен для доступа к рест сервису. Доступ к сервису, который выдает токены должен обязательно быть осуществлен через https соединение, доступ к рест сервису можно сделать через обычный http. Токен должен содержать логин, пароль, так же может содержать expiration time и роли пользователя, а так же любую нужную для вашего приложения информацию. После того как токен готов и к примеру все его параметры разделены двоеточием или другим удобным для вас символом или сериализованы как json или xml объект его необходимо зашифровать, прежде чем отдать пользователю. Учтите, что только рест сервис должен знать как расшифровывать этот токен. После того как токен приходит на рест сервис он его расшифровывает и получает все необходимые данные для аутентификацияя и если надо авторизации рест клиента. Имплементация будет кардинально отличатся от предидущих двух.

Наш security.xml теперь будет выглядеть вот так:

                  

Бин RestAuthenticationEntryPoint будет выглядеть примерно так:

public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint < @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException < response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" ); >> 

Фильтр CustomTokenAuthenticationFilter, который будет проверять валидность токена, права и тд. и в конечном счете решать, позволено ли данному клиенту работать с нашим рест сервисом или нет, будет выглядеть примерно так, но вы можете его реализовать по другому.

public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter < private AuthenticationManager authenticationManager; @Autowired private CryptService cryptService; //service which can decrypt token public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) < super(defaultFilterProcessesUrl); this.authenticationManager = authenticationManager; super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); setAuthenticationManager(new NoOpAuthenticationManager()); setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler()); >public final String HEADER_SECURITY_TOKEN = "My-Rest-Token"; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException < String token = request.getHeader(HEADER_SECURITY_TOKEN); Authentication userAuthenticationToken = parseToken(token); if (userAuthenticationToken == null) < throw new AuthenticationServiceException("here we throw some exception or text"); >return userAuthenticationToken; > @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException < super.successfulAuthentication(request, response, chain, authResult); chain.doFilter(request, response); >// This method makes some validation depend on your application logic private Authentication parseToken(String tokenString) < try < String encryptedToken = cryptService.decrypt(tokenString); Token token = new ObjectMapper().readValue(encryptedToken, Token.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(token.getUsername(), token.getPassword())); >catch (Exception e) < return null; >return null; > > 

Что мы имеем в итоге. Юзер логинится в приложение получает зашифрованный токен, который может использовать спринговый RestTemplate или другой рест клиент добавляя его в хидер, к примеру наш кастомный хидер My-Rest-Token. На стороне сервера фильтр получает значение из этого хидера, расшифровывает токен, парсит его или разберает на составляющие и решает давать или нет доступ клиенту.

Способ четвертый: Digital Signature (public/private key pair)

  1. Когда регестрируется новый пользователь на сервере генерируется пара ключей для этого пользователя — публичный и приватный
  2. Приватный отсылается пользователю и только он сможет расшифровать сообщение (ключ должен отправляться по безопасному каналу, чтобы никто не мог его перехватить)
  3. При каждом рест запросе клиент передает свой логин, чтобы сервис мог зашифровать сообщение нужным публичным ключом
  4. Сервис шифрует и отправляет сообщение
  5. Клиент принимает его и расшифровывает своим ключом

Еще более безопасным можно сделать этот подход если генерировать пару ключей на клиентской стороне с использование javascript библиотек таких как forge. Такой подход позволяет вообще не пересылать приватный ключ по сети, а сразу генерировать на клиентской стороне, что значительно уменьшает риск скомпрометировать этот ключ. Публичный ключ отправляется серверу для дальнейшего использования при шифровании сообщений. Канал для отправки может быть незащищенным, так как нет ничего страшного если публичный ключ будет перехвачен (детали смотри по ссылке выше криптосистемы с открытым ключом).

Способ пятый: Certificate Authentication

  • Trusted — те который может проверить каждый и они зарегистрированы в едином сертификацонном центре
  • Self signed — те которые вы генерите сами и их надо добавлять вашему рест сервису в исключения, чтобы он знал о их существовании и что им можно доверять

generate client and server keys
keytool -genkey -keystore keystore_client -alias clientKey
keytool -genkey -keystore keystore_server -alias serverKey

generate client and server certificates
keytool -export -alias clientKey -rfc -keystore keystore_client > client.cert
keytool -export -alias serverKey -rfc -keystore keystore_server > server.cert

import certificates to corresponding truststores
keytool -import -alias clientCert -file client.cert -keystore truststore_server
keytool -import -alias serverCert -file server.cert -keystore truststore_client

Теперь полученные сертификаты надо добавить в конфигурацию нашего сервера. В данном, случае используется Tomcat

/conf/cert/keystore_server" keystorePass="changeit" truststoreFile="$/conf/cert/truststore_server" truststorePass="changeit" clientAuth="true" sslProtocol="TLS" /> 

Ниже приведен рест клиент с использованием Http Apache Client, который способен предоставить сертификат рест сервису и осуществит все необходимые «рукопожатия» для получения ответа от сервера

public class CertificateAuthenticationServiceImpl implements CertificateAuthenticationService < private static final String keyStorePass = "changeit"; private static final String trustedStorePass = "changeit"; private static final File keyStore = new File(new CertificateAuthenticationServiceImpl().getClass().getResource("/authCertificate/keystore_client").getPath()); private static final File trustedStore = new File(new CertificateAuthenticationServiceImpl().getClass().getResource("/authCertificate/truststore_client").getPath()); private static final String certificateType = "jks"; public String httpGet(URL url) < String resp = null; try < final HttpParams httpParams = new BasicHttpParams(); final KeyStore keystore = KeyStore.getInstance(certificateType); keystore.load(new FileInputStream(keyStore), keyStorePass.toCharArray()); final KeyStore truststore = KeyStore.getInstance(certificateType); truststore.load(new FileInputStream(trustedStore), trustedStorePass.toCharArray()); final SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme(url.toURI().getScheme(), new SSLSocketFactory(keystore, keyStorePass, truststore), url.getPort())); final DefaultHttpClient httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, schemeRegistry), httpParams); try < HttpGet httpget = new HttpGet(url.toString()); CloseableHttpResponse response = httpClient.execute(httpget); try < HttpEntity entity = response.getEntity(); if (entity != null) < resp = EntityUtils.toString(entity); >EntityUtils.consume(entity); > finally < response.close(); >> finally < httpClient.close(); >> catch (Exception e) < throw new RuntimeException(e); >return resp; > > 

Способ шестой: OAuth2 authorization

Ну и на закуску я оставил самый сложны для понимания и реализации способ. Зато он очень гибкий и хорошо подходит для больших порталов. Опять же не буду заниматься копипастом, чтобы почитать, что такое OAuth и как он работает идем сюда.
Spring security предоставляет нам класс OAuthTemplate, который значительно облегчает нам жизнь.
Все идеи для реализации своей OAuth имплементации я почерпнул из этой замечательной статьи там даже есть рабочий проект, который можно скачать.

Заключение

Что ж, я надеюсь мне немного удалось прояснить общую картину и помочь Вам в реализации собственных проектов. Это конечно же не все способы защитить Ваш рест сервис, но есть решения на любой вкус. Примеры реализаций не являются общими решениями, а лишь приведены для полного понимания картины. Вы можете писать свои имплементации в зависимости от нужд проекта.

Перед выбором security для вашего проекта четко решите, что вам надо, взвесьте все за и против. Не стоит усложнять систему, если этого не требует проект.

Надеюсь, Ваши приложения будут безопасными и надёжными.

Источник

Читайте также:  Python full screen window
Оцените статью