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)
- Когда регестрируется новый пользователь на сервере генерируется пара ключей для этого пользователя — публичный и приватный
- Приватный отсылается пользователю и только он сможет расшифровать сообщение (ключ должен отправляться по безопасному каналу, чтобы никто не мог его перехватить)
- При каждом рест запросе клиент передает свой логин, чтобы сервис мог зашифровать сообщение нужным публичным ключом
- Сервис шифрует и отправляет сообщение
- Клиент принимает его и расшифровывает своим ключом
Еще более безопасным можно сделать этот подход если генерировать пару ключей на клиентской стороне с использование 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 для вашего проекта четко решите, что вам надо, взвесьте все за и против. Не стоит усложнять систему, если этого не требует проект.
Надеюсь, Ваши приложения будут безопасными и надёжными.
- Информационная безопасность
- Java
Ответы на вопросы на собеседование Spring Framework (часть 3).
Ключевым интерфейсом в Spring MVC является Controller. Контроллер обрабатывает запросы к действиям, осуществляемые пользователями в пользовательском интерфейсе, взаимодействуя с уровнем обслуживания, обновляя модель и направляя пользователей на соответствующие представления в зависимости от результатов выполнения. Controller - управление, связь между моделью и видом.
Основным контроллером в Spring MVC является org.springframework.web.servlet.DispatcherServlet. Задается аннотацией @Controller и часто используется с аннотацией @RequestMapping, которая указывает какие запросы будут обрабатываться этим контроллером.
- Какая разница между аннотациями @Component, @Repository и @Service в Spring?
@Component - используется для указания класса в качестве компонента spring. При использовании поиска аннотаций, такой класс будет сконфигурирован как spring bean.
@Controller - специальный тип класса, применяемый в MVC приложениях. Обрабатывает запросы и часто используется с аннотацией @RequestMapping.
@Repository - указывает, что класс используется для работы с поиском, получением и хранением данных. Аннотация может использоваться для реализации шаблона DAO.
@Service - указывает, что класс является сервисом для реализации бизнес логики (на самом деле не отличается от Component, но просто помогает разработчику указать смысловую нагрузку класса).
Для указания контейнеру на класс-бин можно использовать любую из этих аннотаций. Но различные имена позволяют различать назначение того или иного класса.
- Расскажите, что вы знаете о DispatcherServlet и ContextLoaderListener.
DispatcherServlet - сервлет диспатчер. Этот сервлет анализирует запросы и направляет их соответствующему контроллеру для обработки. В Spring MVC класс DispatcherServlet является центральным сервлетом, который получает запросы и направляет их соответствующим контроллерам. В приложении Spring MVC может существовать произвольное количество экземпляров DispatcherServlet, предназначенных для разных целей (например, для обработки запросов пользовательского интерфейса, запросов веб-служб REST и т.д.). Каждый экземпляр DispatcherServlet имеет собственную конфигурацию WebApplicationContext, которая определяет характеристики уровня сервлета, такие как контроллеры, поддерживающие сервлет, отображение обработчиков, распознавание представлений, интернационализация, оформление темами, проверка достоверности, преобразование типов и форматирование и т.п.
ContextLoaderListener - слушатель при старте и завершении корневого класса Spring WebApplicationContext. Основным назначением является связывание жизненного цикла ApplicationContext и ServletContext, а так же автоматического создания ApplicationContext. Можно использовать этот класс для доступа к бинам из различных контекстов спринг. Настраивается в web.xml:
- Что такое ViewResolver в Spring?
ViewResolver - распознаватель представлений. Интерфейс ViewResolver в Spring MVC (из пакета org.springframework.web.servlet) поддерживает распознавание представлений на основе логического имени, возвращаемого контроллером. Для поддержки различных механизмов распознавания представлений предусмотрено множество классов реализации. Например, класс UrlBasedViewResolver поддерживает прямое преобразование логических имен в URL. Класс ContentNegotiatingViewResolver поддерживает динамическое распознавание представлений в зависимости от типа медиа, поддерживаемого клиентом (XML, PDF, JSON и т.д.). Существует также несколько реализаций для интеграции с различными технологиями представлений, такими как FreeMarker (FreeMarkerViewResolver), Velocity (VelocityViewResolver) и JasperReports (JasperReportsViewResolver).
InternalResourceViewResolver - реализация ViewResolver, которая позволяет находить представления, которые возвращает контроллер для последующего перехода к нему. Ищет по заданному пути, префиксу, суффиксу и имени.
- Что такое MultipartResolver и когда его использовать?
Интерфейс MultipartResolver используется для загрузки файлов. Существуют две реализации: CommonsMultipartResolver и StandardServletMultipartResolver, которые позволяют фреймворку загружать файлы. По умолчанию этот интерфейс не включается в приложении и необходимо указывать его в файле конфигурации. После настройки любой запрос о загрузке будет отправляться этому интерфейсу.
- Как загрузить файл в Spring MVC?
Внутри спринг предусмотрен интерфейс MultipartResolver для обеспечения загрузки файлов. Фактически нужно настроить файл конфигурации для указания обработчика загрузки файлов, а затем задать необходимый метод в контроллере spring.
- Как обрабатывать исключения в Spring MVC Framework?
В Spring MVC интерфейс HandlerExceptionResolver (из пакета org.springframework.web.servlet) предназначен для работы с непредвиденными исключениями, возникающими во время выполнения обработчиков. По умолчанию DispatcherServlet регистрирует класс DefaultHandlerExceptionResolver (из пакета org.springframework.web.servlet.mvc.support). Этот распознаватель обрабатывает определенные стандартные исключения Spring MVC, устанавливая специальный код состояния ответа. Можно также реализовать собственный обработчик исключений, аннотировав метод контроллера с помощью аннотации @ExceptionHandler и передав ей в качестве атрибута тип исключения. В общем случае обработку исключений можно описать таким образом:
Controller Based - указать методы для обработки исключения в классе контроллере. Для этого нужно пометить такие методы аннотацией @ExceptionHandler.
Global Exception Handler - для обработки глобальных исключений spring предоставляет аннотацию @ControllerAdvice.
HandlerExceptionResolver implementation – общие исключений большая часть времени обслуживают статические страницы. Spring Framework предоставляет интерфейс HandlerExceptionResolver, который позволяет задать глобального обработчика исключений. Реализацию этого интерфейса можно использовать для создания собственных глобальных обработчиков исключений в приложении.
- Каковы минимальные настройки, чтобы создать приложение Spring MVC?
Для создания простого Spring MVC приложения необходимо пройти следующие шаги:
- Добавить зависимости spring-context и spring-webmvc в проект.
- Указать DispatcherServlet в web.xml для обработки запросов внутри приложения.
- Задать определение spring bean (аннотацией или в xml).
- Добавить определение view resolver для представлений.
- Настроить класс контроллер для обработки клиентских запросов.
- Как бы вы связали Spring MVC Framework и архитектуру MVC?
Модель (Model) - выступает любой Java bean в Spring. Внутри класса могут быть заданы различные атрибуты и свойства для использования в представлении.
Преставление (View) - JSP страница, HTML файл и т.п. служат для отображения необходимой информации пользователю. Представление передает обработку запросов к диспетчеру сервлетов (контроллеру).
DispatcherServlet (Controller) - это главный контроллер в приложении Spring MVC, который обрабатывает все входящие запросы и передает их для обработки в различные методы в контроллеры.
- Как добиться локализации в приложениях Spring MVC?
Spring MVC предоставляет очень простую и удобную возможность локализации приложения. Для этого необходимо сделать следующее:
- Создать файл resource bundle, в котором будут заданы различные варианты локализированной информации.
- Определить messageSource в конфигурации Spring используя классы ResourceBundleMessageSource или ResourceBundleMessageSource.
- Определить localceResolver класса CookieLocaleResolver для включения возможности переключения локали.
- С помощью элемента spring:message DispatcherServlet будет определять в каком месте необходимо подставлять локализированное сообщение в ответе.
- Как мы можем использовать Spring для создания веб-службы RESTful, возвращающей JSON?
Spring Framework позволяет создавать Resful веб сервисы и возвращать данные в формате JSON. Spring обеспечивает интеграцию с Jackson JSON API для возможности отправки JSON ответов в resful web сервисе. Для отправки ответа в формате JSON из Spring MVC приложения необходимо произвести следующие настройки:
Добавить зависимости Jackson JSON. С помощью maven это делается так:
Настроить бин RequestMappingHandlerAdapter в файле конфигурации Spring и задать свойство messageConverters на использование бина MappingJackson2HttpMessageConverter.
В контроллере указать с помощью аннотации @ResponseBody возвращение Object:
- Как проверить (валидировать) данные формы в Spring Web MVC Framework?
Spring поддерживает аннотации валидации из JSR-303, а так же возможность создания своих реализаций классов валидаторов. Пример использования аннотаций:
- Что вы знаете Spring MVC Interceptor и как он используется?
Перехватчики в Spring (Spring Interceptor) являются аналогом Servlet Filter и позволяют перехватывать запросы клиента и обрабатывать их. Перехватить запрос клиента можно в трех местах: preHandle, postHandle и afterCompletion.
- preHandle - метод используется для обработки запросов, которые еще не были переданы в метода обработчик контроллера. Должен вернуть true для передачи следующему перехватчику или в handler method. False укажет на обработку запроса самим обработчиком и отсутствию необходимости передавать его дальше. Метод имеет возможность выкидывать исключения и пересылать ошибки к представлению.
- postHandle - вызывается после handler method, но до обработки DispatcherServlet для передачи представлению. Может использоваться для добавления параметров в объект ModelAndView.
- afterCompletion - вызывается после отрисовки представления.
Для создания обработчика необходимо расширить абстрактный класс HandlerInterceptorAdapter или реализовать интерфейс HandlerInterceptor. Так же нужно указать перехватчики в конфигурационном файле Spring.
- Расскажите о Spring Security.
Проект Spring Security предоставляет широкие возможности для защиты приложения. Кроме стандартных настроек для аутентификации, авторизации и распределения ролей и маппинга доступных страниц, ссылок и т.п., предоставляет защиту от различных вариантов атак (например CSRF). Имеет множество различных настроек, но остается легким в использовании.
Перенаправление вошедших в систему пользователей с помощью Spring Security
Веб-сайты обычно не позволяют своим пользователям посещать логин, когда они уже вошли в систему. Обычный способ сделать это — перенаправить пользователя на другую страницу, обычно начальную точку приложения после входа в систему.
В этом руководстве мы рассмотрим несколько способов реализации этого решения с помощью Spring Security.
Кроме того, чтобы узнать больше о том, как мы можем быстро реализовать вход в систему, мы можем начать с этой статьи .
2. Проверка подлинности
Во-первых, нам понадобится метод для проверки подлинности.
Другими словами, нам нужно получить данные аутентификации из SecurityContext и проверить, вошел ли пользователь в систему :
private boolean isAuthenticated() Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || AnonymousAuthenticationToken.class. isAssignableFrom(authentication.getClass())) return false; > return authentication.isAuthenticated(); >
Мы будем использовать это во всех следующих компонентах, отвечающих за перенаправление.
3. Перенаправление с контроллера входа
Самый простой способ достичь нашей цели — определить конечную точку для страницы входа в контроллер.
Нам также нужно будет вернуть определенную страницу, если пользователь аутентифицирован, и страницу входа в противном случае:
@GetMapping("/loginUser") public String getUserLoginPage() if (isAuthenticated()) return "redirect:userMainPage"; > return "loginUser"; >
4. Использование перехватчика
Другой способ перенаправления пользователей — через перехватчик на URI страницы входа.
Перехватчик перехватит запрос до того, как он поступит на контроллер. Таким образом, мы можем решить на основе аутентификации, позволить ли нам идти дальше или мы заблокируем его и вернем ответ о перенаправлении.
Если пользователь аутентифицирован, нам нужно будет изменить две вещи в ответе:
- Установите код состояния HttpStatus.SC_TEMPORARY_REDIRECT.
- Добавьте заголовок Location с URL-адресом перенаправления
И, наконец, мы прервем цепочку выполнения, вернув false :
public class LoginPageInterceptor implements HandlerInterceptor UrlPathHelper urlPathHelper = new UrlPathHelper(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) if ("/loginUser".equals(urlPathHelper.getLookupPathForRequest(request)) && isAuthenticated()) String encodedRedirectURL = response.encodeRedirectURL( request.getContextPath() + "/userMainPage"); response.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT); response.setHeader("Location", encodedRedirectURL); return false; > else return true; > > // isAuthenticated method >
Нам также нужно добавить перехватчик в жизненный цикл Spring MVC :
@Configuration public class LoginRedirectMvcConfig implements WebMvcConfigurer @Override public void addInterceptors(InterceptorRegistry registry) registry.addInterceptor(new LoginPageInterceptor()); > >
Мы могли бы использовать конфигурацию Spring на основе XML-схемы, чтобы добиться того же:
mvc:interceptors> mvc:interceptor> mvc:mapping path="/loginUser"/> bean class="com.foreach.loginredirect.LoginPageInterceptor"/> mvc:interceptor> mvc:interceptors>
5. Использование фильтра
Точно так же мы можем реализовать фильтр Spring.
Фильтр можно напрямую применить к SecurityContext , используя цепочку фильтров Spring Security. Таким образом, он может перехватить запрос сразу после создания аутентификации.
Давайте расширим GenericFilterBean, переопределим метод doFilter и проверим аутентификацию:
public class LoginPageFilter extends GenericFilterBean @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException HttpServletRequest servletRequest = (HttpServletRequest) request; HttpServletResponse servletResponse = (HttpServletResponse) response; if (isAuthenticated() && "/loginUser".equals(servletRequest.getRequestURI())) String encodedRedirectURL = ((HttpServletResponse) response).encodeRedirectURL( servletRequest.getContextPath() + "/userMainPage"); servletResponse.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT); servletResponse.setHeader("Location", encodedRedirectURL); > chain.doFilter(servletRequest, servletResponse); > // isAuthenticated method >
Нам нужно добавить фильтр после UsernamePasswordAuthenticationFilter в цепочке фильтров.
Кроме того, нам нужно будет авторизовать запрос URI страницы входа, чтобы включить для него цепочку фильтров:
@Configuration @EnableWebSecurity public class LoginRedirectSecurityConfig extends WebSecurityConfigurerAdapter @Override protected void configure(HttpSecurity http) throws Exception http .addFilterAfter(new LoginPageFilter(), UsernamePasswordAuthenticationFilter.class) .authorizeRequests().antMatchers("/loginUser").permitAll() // Other security configuration > >
Наконец, если мы решим использовать XML-конфигурацию, мы можем определить bean-компонент для фильтра и добавить его в цепочку фильтров в HTTP - теге безопасности:
beans:bean id="loginPageFilter" class="com.foreach.loginredirect.LoginPageFilter"/> security:http pattern="/**" use-expressions="true" auto-config="true"> security:intercept-url pattern="/loginUser" access="permitAll"/> security:custom-filter after="BASIC_AUTH_FILTER" ref="loginPageFilter"/> security:http>
Краткое руководство по созданию пользовательского фильтра для Spring Security можно найти здесь .
6. Заключение
В этом руководстве мы рассмотрели несколько способов перенаправления уже вошедших пользователей со страницы входа с помощью Spring Security.
Как всегда, полный исходный код, используемый в этом руководстве, доступен на GitHub .
Другой учебник, который может представлять интерес, — « Перенаправление на разные страницы после входа в систему с помощью Spring Security », в котором мы узнаем, как перенаправлять пользователей разных типов на определенные страницы.
- 1. Обзор
- 2. Проверка подлинности
- 3. Перенаправление с контроллера входа
- 4. Использование перехватчика
- 5. Использование фильтра
- 6. Заключение
Добавление Spring Security в проект — настройки по умолчанию
Сгенерируем на https://start.spring.io/ Spring Boot приложение с зависимостью Web:
org.springframework.boot spring-boot-starter-web
Напишем в нем единственный REST-контроллер:
@RestController public class HelloController < @GetMapping("/api/hello") public String hello()< return "Hello"; >>
Сейчас к нему имеют доступ все:
Добавление Spring Security
Для того, чтобы включить Spring Security, достаточно добавить Maven-зависимость:
org.springframework.boot spring-boot-starter-security
И сразу же мы столкнемся с неожиданностью. Теперь при попытке ввести в браузере http://localhost:8080/api/hello мы перенаправляемся на страницу логина http://localhost:8080/login. Если ввести в нее любые наугад взятые данные, получим ошибку:
Отсюда очевидно, что некая проверка выполняется. Но какая?
Что дает зависимость spring-boot-starter-security
Обычно включение любого стартера в POM-файл ничего не дает: чтобы что-то запрограммировать, все равно надо написать дополнительный код. В случае Spring Security все иначе.
Давайте заглянем в консоль. В ней видно, что генерируется некий пароль:
Да, Spring Security создал некоего пользователя по умолчанию. Имя его user, а пароль генерируется автоматически при запуске программы.
Итак, что происходит при одном только добавлении spring-boot-starter-security в POM-файл:
- Spring Security создает пользователя с именем user и автоматически сгенерированным паролем, который можно посмотреть в консоли.
- Создается страница с формой для ввода имени и пароля -имеем Form-based аутентификацию.
- Имя и пароль реально проверяются.
- Все url оказываются недоступны, пока мы не «залогинимся» под этим пользователем.
- И еще создается страница, где можно «разлогиниться». Она находится по адресу logout. Выглядит так:
Кстати, страница входа генерируется в классе org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
In-Memory аутентификация
С точки зрения получения параметров пользователя из запроса, продемонстрированная выше аутентификация является Form-Based — имя и пароль отправляются через форму и берутся на сервере из запроса как POST-параметры.
С точки зрения же хранения пользователей на стороне сервера, продемонстрированная выше аутентификация в Spring Security называется In-Memory authentication. Она означает, что пользователь хранится не в базе, не на LDAP-сервере и не где-либо еще, а в оперативной памяти приложения до тех пор, пока оно запущено. И чтобы отредактировать пользователя, придется заново запускать приложение. Разумеется, этот вариант не годится для среды Production, зато он прост и полезен для экспериментов во время разработки.
Как задать своего пользователя в In-Memory аутентификации
Итак, приложение при запуске генерирует и хранит имя и пароль пользователя в памяти, мы можем подсмотреть пароль в консоли.
Но чтобы не подсматривать пароль в консоли, можно воспользоваться файлом настроек application.yml — зафиксировать имя/пароль там.
Переопределение пользователя и пароля в настройках
Для этого в настройках application.yml нужно задать свойства:
spring: security: user: name: myname password: mypassword
Теперь пароль не генерируется и в консоль не выводится — используется пользователь с именем и паролем, заданным в application.yml .
Можно задать и несколько пользователей — давайте сделаем это в коде. Перейдем наконец к написанию кода — напишем класс-конфигурацию для Spring Security и настроим в нем аутентификацию явно.
Настройка In-Memory аутентификации в коде
Итак, создадим класс SecurityConfig, который расширяет класс WebSecurityConfigurerAdapter. Сделаем его бином с помощью @EnableWebSecurity:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter
Аннотацию @EnableWebSecurity необходимо прописывать при настройке аутентификации, а иначе, как сказано в документации, поведение будет непредсказуемым. Хотя наш пример работает и просто с @Configuration.
Аутентификацию выполняет AuthenticationManager, но определять этот бин явно нам не надо. Вместо этого надо переопределить метод configure(AuthenticationManagerBuilder auth) класса WebSecurityConfigurerAdapter — так мы получим доступ к билдеру AuthenticationManagerBuilder, а уж через него настроим нужный нам AuthenticationManager. Делается это так:
- Во первых, в билдере надо задать тип аутентификации — она может быть не In-Memory, а другой: например, Jdbc, LDAP или кастомной (тип аутинтификации задает где в принципе хранится пользователь). У нас In-Memory аутентификация — этот факт задается строкой auth.inMemoryAuthentication().
- Далее идут специфические настройки выбранного AuthenticationManager. В них уточняется, как AuthenticationManager извлекает хранимого пользователя, чтоб потом сравнить его с введенным. В случае In-Memory аутентификации менеджеру далеко ходить не надо, реальные имя и пароль задаются тут же с помощью withUser() и password():
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter < @Bean public PasswordEncoder passwordEncoder() < return NoOpPasswordEncoder.getInstance(); >@Override public void configure(AuthenticationManagerBuilder auth) throws Exception < auth.inMemoryAuthentication() .withUser("u1") .password("p1") .authorities("ROLE_USER") .and() .withUser("u2") .password("p2") .authorities("ROLE_USER"); >>
На самом деле AuthenticationManager достает не только реальные имя и пароль, но еще разрешение пользователя (что ему разрешено делать в приложении). Мы задали двух пользователей с разрешением ROLE_USER. В данном примере разрешения не используются, мы будем их использовать в примере про авторизацию.
Итак, мы настроили AuthenticationManager, который сравнивает переданные имя и пароль со значениями имени и пароля u1 p1 и u2 p2. В случае совпадения с любым из пользователей, аутентификация проходит успешно.
Обратите внимание на бин PasswordEncoder — в нем задается, как шифровать пароль. Мы задали NoOpPasswordEncoder, который не делает ничего — оставляет пароль в первоначальном виде. Это выбрано в учебных целях, чтобы было наглядно, что требуется вводить в форму логина при запуске примера — ведь в методе password(«p2») задается уже зашифрованный пароль. Конечно, в реальном приложении NoOpPasswordEncoder не пригоден — пароль нужно шифровать, например, с помощью BCryptPasswordEncoder.
Итоги
Таким образом, в примере мы вручную воспроизвели In-Memory аутентификацию, которая вообще-то предоставляется по умолчанию при добавлении security-стартера в проект. Правда, немного видоизменили ее, добавив двух своих пользователей.
Код примера есть на GitHub, в следующей статье настроим авторизацию.
Автор sysout Опубликовано 23.03.2020 26.05.2020 Рубрики Spring, Spring Security
Добавление Spring Security в проект — настройки по умолчанию: 3 комментария
В одном листинге указано, что мы имплементируем WebMvcConfigurer, в другом уже нет. Так его необходимо все-таки имплементировать? И для чего?