总览
最近,我正在一个使用REST服务层与客户端应用程序(GWT应用程序)进行通信的项目中。 因此,我花了很多时间来弄清楚如何使用Spring Security保护REST服务。 本文介绍了我找到的解决方案,并已实现。 我希望此解决方案将对某人有所帮助,并节省大量宝贵的时间。
解决方案
在普通的Web应用程序中,每当访问安全资源时,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保护的普通Web应用程序一样,在访问受保护的服务之前,必须首先通过向登录URL提交密码和用户名来进行身份验证。
注意:以下解决方案要求Spring Security的最低版本为3.2。
覆盖AuthenticationEntryPoint
该类扩展了org.springframework.security.web.AuthenticationEntryPoint,并且仅实现了一种方法,该方法会在未经授权的情况下发送响应错误(带有401状态代码)。
@Component
public class HttpAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());}
}
重写AuthenticationSuccessHandler
AuthenticationSuccessHandler负责成功认证后的操作,默认情况下它将重定向到URL,但在我们的情况下,我们希望它发送带有数据的HTTP响应。
@Component
public class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {private static final Logger LOGGER = LoggerFactory.getLogger(AuthSuccessHandler.class);private final ObjectMapper mapper;@AutowiredAuthSuccessHandler(MappingJackson2HttpMessageConverter messageConverter) {this.mapper = messageConverter.getObjectMapper();}@Overridepublic 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,但是在我们的情况下,我们只希望它发送带有401 UNAUTHORIZED代码的HTTP响应。
@Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {@Overridepublic 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,因为我们没有重写它以返回带有20 OK代码的HTTP响应。
@Component
public class HttpLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException {response.setStatus(HttpServletResponse.SC_OK);response.getWriter().flush();}
}
Spring安全配置
这是最后一步,将所有工作放在一起,我更喜欢使用新的方式来配置Spring Security,它使用Java而不是XML,但是您可以轻松地将此配置适应XML。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private static final String LOGIN_PATH = ApiPaths.ROOT + ApiPaths.User.ROOT + ApiPaths.User.LOGIN;@Autowiredprivate NuvolaUserDetailsService userDetailsService;@Autowiredprivate HttpAuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate AuthSuccessHandler authSuccessHandler;@Autowiredprivate AuthFailureHandler authFailureHandler;@Autowiredprivate HttpLogoutSuccessHandler logoutSuccessHandler;@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Bean@Overridepublic UserDetailsService userDetailsServiceBean() throws Exception {return super.userDetailsServiceBean();}@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(userDetailsService);authenticationProvider.setPasswordEncoder(new ShaPasswordEncoder());return authenticationProvider;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(authenticationProvider());}@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Overrideprotected 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 。
我希望这可以帮助一些努力寻找解决方案的开发人员,请随时提出任何问题,或发布任何可以使该解决方案更好的增强功能。
翻译自: https://www.javacodegeeks.com/2014/09/secure-rest-services-using-spring-security.html