总览
最近,我正在一个使用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 {
@Override
public 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;
@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,但是在我们的情况下,我们只希望它发送带有401 UNAUTHORIZED代码的HTTP响应。
@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,因为我们没有重写它以返回带有20 OK代码的HTTP响应。
@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。
@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 。
我希望这可以帮助一些努力寻找解决方案的开发人员,请随时提出任何问题,或发布任何可以使该解决方案更好的增强功能。
翻译自: https://www.javacodegeeks.com/2014/09/secure-rest-services-using-spring-security.html