обзор
Недавно я работал над проектом, который использует уровень сервисов REST для связи с клиентским приложением (приложение GWT). Поэтому я потратил много времени, чтобы выяснить, как защитить REST-сервисы с помощью Spring Security. В этой статье описывается решение, которое я нашел, и я реализовал. Я надеюсь, что это решение будет кому-то полезно и сэкономит много драгоценного времени.
Решение
В обычном веб-приложении всякий раз, когда к защищенному ресурсу обращаются, Spring Security проверяет контекст безопасности для текущего пользователя и решает либо перенаправить его на страницу входа (если пользователь не аутентифицирован), либо переслать его на не авторизованный ресурс страница (если у него нет необходимых разрешений).
В нашем сценарии все по-другому, потому что у нас нет страниц для пересылки, нам нужно адаптировать и переопределить Spring Security для связи только с использованием статуса HTTP-протоколов, ниже я перечисляю, что нужно сделать, чтобы Spring Security работала лучше всего:
- Аутентификация будет осуществляться с помощью обычной формы входа, единственное отличие состоит в том, что ответ будет в JSON вместе с HTTP-статусом, который может быть либо кодом 200 (если аутентификация пройдена), либо кодом 401 (если аутентификация не удалась);
- Переопределите AuthenticationFailureHandler, чтобы вернуть код 401 UNAUTHORIZED;
- Переопределите AuthenticationSuccessHandler, чтобы вернуть код 20 OK, тело ответа HTTP содержит данные JSON текущего аутентифицированного пользователя;
- Переопределите AuthenticationEntryPoint, чтобы всегда возвращать код 401 UNAUTHORIZED. Это заменит поведение по умолчанию Spring Security, которое перенаправляет пользователя на страницу входа в систему, если он не отвечает требованиям безопасности, потому что в REST у нас нет страницы входа;
- Переопределите LogoutSuccessHandler, чтобы вернуть код 20 OK;
Как и в обычном веб-приложении, защищенном Spring Security, перед доступом к защищенной службе необходимо сначала пройти аутентификацию, отправив пароль и имя пользователя в URL-адрес входа.
Примечание. Для следующего решения требуется Spring Security в версии не ниже 3.2.
Переопределение AuthenticationEntryPoint
Класс расширяет org.springframework.security.web.AuthenticationEntryPoint и реализует только один метод, который отправляет ошибку ответа (с кодом состояния 401) в случае неавторизованной попытки.
1
2
3
4
5
6
7
8
|
@Component public class HttpAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); } } |
Переопределение AuthenticationSuccessHandler
AuthenticationSuccessHandler отвечает за то, что делать после успешной аутентификации, по умолчанию он перенаправляет на URL, но в нашем случае мы хотим, чтобы он отправлял HTTP-ответ с данными.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Component public class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AuthSuccessHandler. class ); private final ObjectMapper mapper; @Autowired AuthSuccessHandler(MappingJackson2HttpMessageConverter messageConverter) { this .mapper = messageConverter.getObjectMapper(); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); NuvolaUserDetails userDetails = (NuvolaUserDetails) authentication.getPrincipal(); User user = userDetails.getUser(); userDetails.setUser(user); LOGGER.info(userDetails.getUsername() + " got is connected " ); PrintWriter writer = response.getWriter(); mapper.writeValue(writer, user); writer.flush(); } } |
Переопределение AuthenticationFailureHandler
AuthenticationFaillureHandler отвечает за то, что делать после неудачной аутентификации, по умолчанию он перенаправляет на URL страницы входа, но в нашем случае мы просто хотим, чтобы он отправил HTTP-ответ с кодом 401 UNAUTHORIZED.
01
02
03
04
05
06
07
08
09
10
11
12
|
@Component public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter writer = response.getWriter(); writer.write(exception.getMessage()); writer.flush(); } } |
Переопределение LogoutSuccessHandler
LogoutSuccessHandler решает, что делать, если пользователь успешно вышел из системы, по умолчанию он будет перенаправлять на URL страницы входа в систему, потому что у нас его нет, я переопределил его, чтобы вернуть HTTP-ответ с кодом 20 OK.
1
2
3
4
5
6
7
8
9
|
@Component public class HttpLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { response.setStatus(HttpServletResponse.SC_OK); response.getWriter().flush(); } } |
Конфигурация безопасности Spring
Это последний шаг, чтобы сложить все то, что мы сделали, я предпочитаю использовать новый способ настройки Spring Security с Java без XML, но вы можете легко адаптировать эту конфигурацию к XML.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private static final String LOGIN_PATH = ApiPaths.ROOT + ApiPaths.User.ROOT + ApiPaths.User.LOGIN; @Autowired private NuvolaUserDetailsService userDetailsService; @Autowired private HttpAuthenticationEntryPoint authenticationEntryPoint; @Autowired private AuthSuccessHandler authSuccessHandler; @Autowired private AuthFailureHandler authFailureHandler; @Autowired private HttpLogoutSuccessHandler logoutSuccessHandler; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super .authenticationManagerBean(); } @Bean @Override public UserDetailsService userDetailsServiceBean() throws Exception { return super .userDetailsServiceBean(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); authenticationProvider.setPasswordEncoder( new ShaPasswordEncoder()); return authenticationProvider; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); } @Override protected AuthenticationManager authenticationManager() throws Exception { return super .authenticationManager(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authenticationProvider(authenticationProvider()) .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .and() .formLogin() .permitAll() .loginProcessingUrl(LOGIN_PATH) .usernameParameter(USERNAME) .passwordParameter(PASSWORD) .successHandler(authSuccessHandler) .failureHandler(authFailureHandler) .and() .logout() .permitAll() .logoutRequestMatcher( new AntPathRequestMatcher(LOGIN_PATH, "DELETE" )) .logoutSuccessHandler(logoutSuccessHandler) .and() .sessionManagement() .maximumSessions( 1 ); http.authorizeRequests().anyRequest().authenticated(); } } |
Это был пробный пик в общей конфигурации, я прикрепил в этой статье репозиторий Github, содержащий образец проекта https://github.com/imrabti/gwtp-spring-security .
Я надеюсь, что это поможет некоторым из вас, разработчикам, изо всех сил пытающимся найти решение, пожалуйста, не стесняйтесь задавать любые вопросы или публиковать любые улучшения, которые могут сделать это решение лучше.
Ссылка: | Защищайте REST-сервисы с помощью Spring Security от нашего партнера по JCG Идрисса Мрабти в блоге Fancy UI . |