当前位置: 首页 > 工具软件 > GWT-REST > 使用案例 >

使用Spring Security保护REST服务

吕皓
2023-12-01

总览

最近,我正在一个使用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

 类似资料: