В Spring Security заложено множество возможностей и даже готовых решений в плане авторизации и аутентификации. Но, к сожалению, не всегда стандартный функционал отвечает бизнес требованиям. Поэтому создание собственных механизмов авторизации не такое уж и редкое явление.
В этой статье мы рассмотри создание собственного механизма авторизации на основе токенов.
Сразу отметим, что несмотря на название статьи мы не будем рассматривать в ней генерацию и конкретные механизмы валидации.
Это связано с тем, что:
- Существует целый ряд алгоритмов и подходов для получения токенов (JWT не единственное возможное решение!);
- Механизм валидации токена часто определяется не только и даже не столько типом токена, сколько бизнес логикой проекта.
Поэтому, чтобы не вдаваться в излишние подробности, в статье мы заменим валидацию её заглушкой.
Надеюсь на понимание!
Если вам необходимо запустить операционную систему с флешки, то необходимо знать как отключить secure boot , так как из-за него система может не запуститься. Это мы рассмотрели в отдельном материале.
Общее описание компонентов системы
Как известно система авторизации и аутентификации Spring Security базируется на четырёх основных компонентах:
- Фильтр запроса, который получает токен из соответствующего заголовка запроса и при необходимости выполняет простейшие первичные проверки;
- Объект Authentication, который содержит данные о токене и правах пользователя (работа с правами в статье не рассматривается);
- Менеджер аутентификации (AithenticationManager), который реализует основную логику;
- Класс конфигурации, который выполняет соответствующую настройку Spring Security, включая подключение вышеназванных компонентов.
Рассмотрим их реализацию.
Объект Authentication
В нашем случае это класс наследник абстрактного класса AbstractAuthenticationToken.
Класс довольно простой. Всё, что нам нужно, это сохранить в нём наш токен, как это показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TokenAuthentication extends AbstractAuthenticationToken { private String token; public TokenAuthentication(String token) { super(null); this.token = token; } public TokenAuthentication(String token, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.token = token; } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { return token; } } |
Фильтр запроса
Фильтр будет наследником класса AbstractAuthenticationProcessingFilter.
В фильтре мы будем получать токен из соответствующего заголовка HTTP запроса и проверять не равен ли он null (последнее означает отсутствие токена).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter { protected TokenLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager manager) { super(defaultFilterProcessesUrl); setAuthenticationManager(manager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { // Получаем токен из заголовка запроса. String token = request.getHeader("Authorization"); Authentication auth; // Если токен не равен null (т.е. присутствует в заголовке), передаём его внутри специального объекта в менеджер аутентификации. // Если нет передаём null. if (token != null) { auth = new TokenAuthentication(token); } else { auth = new TokenAuthentication(null); auth.setAuthenticated(false); } return getAuthenticationManager().authenticate(auth); } } |
Менеджер аутентификации
Реализует интерфейс AuthenticationProvider. В нём, по сути, и происходит основная проверка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Component public class TokenAuthenticationProvider implements AuthenticationProvider { // Упомянутая в самом начале статьи заглушка с бизнес логикой валидации токена. @Autowired private TokenValidator validator @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // Проверка токена. Если он прошёл проверку возвращаем его как валидный. if (validator.validate(authentication)) { TokenAuthentication validatedAuthentication = new TokenAuthentication(authentication.getPrincipal().toString()); validatedAuthentication.setAuthenticated(true); return validatedAuthentication; } else { // Если нет, возбуждаем исключение throw new UsernameNotFoundException("Unknown token"); } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(TokenAuthentication.class); } } |
Класс конфигурации
Является наследником класса WebSecurityConfigurerAdapter.
Здесь мы настраиваем Spring Sequrity. Указываем, какие URL требуют авторизации, а какие нет. Подключаем вышеописанные компоненты. Настраиваем сессию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // Менеджер аутентификации. @Autowired protected TokenAuthenticationProvider provider; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() // Запросы, не требующие аутентификации .antMatchers("/").permitAll() // Запросы,требующие аутентификации. .anyRequest().authenticated() // Настройка сессии. .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Устанавливаем код ошибки (401 вместо 403 по умолчанию) .and() .exceptionHandling() .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) // Подключение фильтров. .and() .addFilterAfter(new TokenLoginFilter("/test", authenticationManager()), UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // Подключение менеджера аутентификации. auth.authenticationProvider(provider); } } |
Обратите внимание, что при создании собственного механизма авторизации, необходимо переопределять оба метода configure, чтобы добавить не только фильтр, но и менеджер аутентификации. Иначе ничего работать не будет (во всяком случае правильно).
Также следует отключить сохранение состояния для сессии, как это показано выше (sessionCreationPolicy(SessionCreationPolicy.STATELESS). Иначе при попытке авторизоваться с правильным токеном можно получить ошибку 500 (Internal Server Error).
Резюме
В этой статье мы создали, по сути, общий механизм, который может использоваться для авторизации и аутентификации с использованием любых токенов и в соответствии с любой бизнес логикой.
Всё, что остаётся сделать, так это внести соответствующие доработки для реализации проверки как самого токена, так и прав пользователя. Но, это уже темы для других статей.
Добавить комментарий