Аутентификация С Использованием Токенов Spring Security И Jwt

Всем привет! Хабр жив! Этот пост вряд ли наберет массу просмотров и комментариев, но я надеюсь, что он немного поможет здоровью хаба.

В этой статье мы рассмотрим принцип аутентификации в веб-приложениях на платформе Spring с использованием относительно нового механизма аутентификации — Веб-токен JSON (JWT) .

Этот механизм уже протестирован и реализован для многих языков программирования.

Использование токена позволяет серверу не беспокоиться о сохранении состояния между запросами (HTTP-сессиями) и уменьшает количество запросов к базе данных — необходимые для восстановления данные могут храниться в токене.

Немного о токене JWT: сервер смешивает полезную нагрузку JSON (заголовок и тело) с секретным ключом и генерирует хеш, прикрепляя его в качестве подписи к полезной нагрузке.

Полезная нагрузка закодирована в формате Base64Url, поэтому, естественно, вам не следует передавать секретные данные в токен.

Стандарт JWT не предусматривает шифрование полезной нагрузки.

Если хотите, зашифруйте отдельно сами, а задача токена — только обеспечить аутентификацию.

Предполагается, что читатель знаком с основами Spring Security. Вы можете прочитать о нем Здесь



1).

Генерация токенов

Для моего примера я взял один из реализации Спецификации JWT. Токен генерируется следующим образом:
  
  
  
  
  
   

package com.example.security; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.crypto.MacProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import java.util.*; @Service public class GetTokenServiceImpl implements GetTokenService { @Autowired private UserDetailsService userDetailsService; @Override public TokenObject getToken(String username, String password) throws Exception { if (username == null || password == null) return null; User user = (User) userDetailsService.loadUserByUsername(username); Map<String, Object> tokenData = new HashMap<>(); if (password.equals(user.getPassword())) { tokenData.put("clientType", "user"); tokenData.put("userID", user.getUserId().

toString()); tokenData.put("username", authorizedUser.getUsername()); tokenData.put("token_create_date", new Date().

getTime()); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.YEAR, 100); tokenData.put("token_expiration_date", calendar.getTime()); JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder.setExpiration(calendar.getTime()); jwtBuilder.setClaims(tokenData); String key = "abc123"; String token = jwtBuilder.signWith(SignatureAlgorithm.HS512, key).

compact(); return token; } else { throw new Exception("Authentication error"); } } }

В результате мы получаем строку вида .

.

, который мы отправляем клиенту Теперь о Spring Security. Чтобы реализовать наш собственный механизм аутентификации, нам нужно реализовать свой собственный фильтр И менеджер аутентификации .



2).

Реализация фильтра

Фильтр — это объект класса, реализующий интерфейс.

javax.servlet.Фильтр , который перехватывает запросы определенных URL-адресов и выполняет некоторые действия.

Если фильтров несколько, то они образуют цепочку фильтров — HTTP-запрос после получения приложением проходит через эту цепочку.

Каждый фильтр в цепочке может обработать запрос, передать его следующим фильтрам в цепочке или не передать, сразу отправив ответ клиенту.

Задача нашего фильтра — передать токен из запроса менеджеру аутентификации и, если аутентификация прошла успешно, установить контекст безопасности приложения.



package com.example.security; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public TokenAuthenticationFilter() { super("/rest/**"); setAuthenticationSuccessHandler((request, response, authentication) -> { SecurityContextHolder.getContext().

setAuthentication(authentication); request.getRequestDispatcher(request.getServletPath() + request.getPathInfo()).

forward(request, response); }); setAuthenticationFailureHandler((request, response, authenticationException) -> { response.getOutputStream().

print(authenticationException.getMessage()); }); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String token = request.getHeader("token"); if (token == null) token = request.getParameter("token"); if (token == null) { TokenAuthentication authentication = new TokenAuthentication(null, null); authentication.setAuthenticated(false); return authentication; } TokenAuthentication tokenAuthentication = new TokenAuthentication(token); Authentication authentication = getAuthenticationManager().

authenticate(tokenAuthentication); return authentication; } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { super.doFilter(req, res, chain); } }

Мы унаследовали от абстрактного класса org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter , который специально разработан для аутентификации.

Если URL-адрес запроса соответствует шаблону "/отдых/**" функция будет вызвана автоматически попыткаАутентификация() .

Также мы установили в конструктор два обработчика — Обработчик успеха аутентификации И Обработчик сбоя аутентификации .

Если попытка аутентификации вернет объект Аутентификация , тогда сработает первый обработчик, второй обработчик сработает при вызове метода попытка аутентификации исключения Исключение аутентификации .

Как мы видим, после успешной аутентификации мы устанавливаем контекст безопасности приложения с помощью SecurityContextHolder.getContext().

setAuthentication(аутентификация) .

Контекст, установленный таким образом, является переменной ThreadLocal , т.е.

доступен до тех пор, пока жив поток работы с клиентом.

После установки контекста мы направляем запрос пользователя на сервлет с первоначально запрошенным URL-адресом.



3).

Менеджер аутентификации.

Менеджер аутентификации — это объект класса, реализующий интерфейс org.springframework.security.authentication.AuthenticationManager только с одним методом аутентифицировать() .

Этому методу необходимо передать частично заполненный объект, реализующий интерфейс.

org.springframework.security.core.Authentication (контекст безопасности приложения).

Задача менеджера аутентификации — заполнить весь объект в случае успешной аутентификации.

Аутентификация и верните его.

При заполнении необходимо указать пользователя ( главный ), его права ( власти ), выполнять setAuthenticated (истина) .

Если это не удается, менеджер аутентификации должен выдать исключение.

Исключение аутентификации .

Приведем пример реализации интерфейса org.springframework.security.core.Authentication :

package com.example.security; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.servlet.http.HttpServletRequest; import java.util.Collection; import java.util.List; import java.util.Map; public class TokenAuthentication implements Authentication { private String token; private Collection<? extends GrantedAuthority> authorities; private boolean isAuthenticated; private UserDetails principal; public TokenAuthentication(String token) { this.token = token; this.details = request; } public TokenAuthentication(String token, Collection<SimpleGrantedAutority> authorities, boolean isAuthenticated, UserDetails principal) { this.token = token; this.authorities = authorities; this.isAuthenticated = isAuthenticated; this.principal = principal; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public Object getCredentials() { return null; } @Override public Object getDetails() { return details; } @Override public String getName() { if (principal != null) return ((UserDetails) principal).

getUsername(); else return null; } @Override public Object getPrincipal() { return principal; } @Override public boolean isAuthenticated() { return isAuthenticated; } @Override public void setAuthenticated(boolean b) throws IllegalArgumentException { isAuthenticated = b; } public String getToken() { return token; } }

Вот реализация менеджера аутентификации:

package com.example.security; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.DefaultClaims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.security.core.GrantedAuthority; import javax.servlet.http.HttpServletRequest; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @Service public class TokenAuthenticationManager implements AuthenticationManager { @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { try { if (authentication instanceof TokenAuthentication) { TokenAuthentication readyTokenAuthentication = processAuthentication((TokenAuthentication) authentication); return readyTokenAuthentication; } else { authentication.setAuthenticated(false); return authentication; } } catch (Exception ex) { if(ex instanceof AuthenticationServiceException) throw ex; } } private TokenAuthentication processAuthentication(TokenAuthentication authentication) throws AuthenticationException { String token = authentication.getToken(); String key = "key123"; DefaultClaims claims; try { claims = (DefaultClaims) Jwts.parser().

setSigningKey(key).

parse(token).

getBody(); } catch (Exception ex) { throw new AuthenticationServiceException("Token corrupted"); } if (claims.get("TOKEN_EXPIRATION_DATE", Long.class) == null) throw new AuthenticationServiceException("Invalid token"); Date expiredDate = new Date(claims.get("TOKEN_EXPIRATION_DATE", Long.class)); if (expiredDate.after(new Date())) return buildFullTokenAuthentication(authentication, claims); else throw new AuthenticationServiceException("Token expired date error"); } private TokenAuthentication buildFullTokenAuthentication(TokenAuthentication authentication, DefaultClaims claims) { User user = (User) userDetailsService.loadUserByUsername(claims.get("USERNAME", String.class)); if (user.isEnabled()) { Collection<GrantedAutority> authorities = user.getAuthorities(); TokenAuthentication fullTokenAuthentication = new TokenAuthentication(authentication.getToken(), authorities, true, user); return fullTokenAuthentication; } else { throw new AuthenticationServiceException("User disabled");; } } }



4).

Как собрать все это вместе

Во-первых, необходимо установить фильтр.

Вы можете сделать это двумя способами Первый способ — определить фильтр в файле веб.

xml наше приложение

<filter> <filter-name>springSecurityTokenFilter</filter-name> <filter-class>com.example.security.TokenAuthenticationFilter</filter-class> </filter> <filter-mapping> <filter-name>springSecurityTokenFilter</filter-name> <url-pattern>/rest/**</url-pattern> </filter-mapping>

При использовании этого метода необходимо сразу указать менеджер аутентификации в конструкторе фильтра, поскольку экземпляр фильтра не будет доступен в контексте приложения Spring. Если вам нужен фильтр или менеджер аутентификации в качестве bean-компонентов Spring, вам нужно использовать второй метод. Второй способ — установить фильтр в конфигурации Spring Security. Например, давайте покажем конфигурацию с помощью Java Config.

package com.example.security; import com.example.security.RestTokenAuthenticationFilter; import com.example.security.TokenAuthenticationManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("userDetailsService") UserDetailsService userDetailsService; @Autowired TokenAuthenticationManager tokenAuthenticationManager; @Override protected void configure(HttpSecurity http) throws Exception { http .

headers().

frameOptions().

sameOrigin() .

and() .

addFilterAfter(restTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .

authorizeRequests() .

antMatchers("/rest/*").

authenticated() } @Bean(name = "restTokenAuthenticationFilter") public RestTokenAuthenticationFilter restTokenAuthenticationFilter() { RestTokenAuthenticationFilter restTokenAuthenticationFilter = new RestTokenAuthenticationFilter(); tokenAuthenticationManager.setUserDetailsService(userDetailsService); restTokenAuthenticationFilter.setAuthenticationManager(tokenAuthenticationManager); return restTokenAuthenticationFilter; } }

В соответствии .

addFilterAfter(restTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) мы добавили наш фильтр в цепочку фильтров после стандартный фильтр Имя пользователяПарольAuthenticationFilter .

На этом базовая настройка механизма аутентификации Spring Security с использованием JSON Web Token завершена.

Желаю всем успехов! Спасибо за внимание! Теги: #java #spring Security #аутентификация #токен #java

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.