打造REST风格的Spring Security配置

2023-03-07 16:28:17 浏览数 (1)

原文链接:https://www.baeldung.com/securing-a-restful-web-service-with-spring-security

作者: Eugen Paraschiv

译者: helloworldtang

目录

  • 1.概览
  • 2.在web.xml中配置Spring Security
  • 3. 使用XML配置Spring Security
  • 3.1. <http>组件
  • 3.2. 认证入口点
  • 3.3. 适合REST的<form-login>配置
  • 3.4. 认证成功返回200而不是301
  • 3.5. 认证失败返回 401 而不是 302
  • 3.6. AuthenticationManager和Provider
  • 3.7. 最后–––针对REST服务的身份认证
  • 4. 使用Java配置来配置Spring Security
  • 5. Maven和其它问题
  • 6. 总结

1.概览

本教程介绍如何使用Spring和基于Java配置的Spring Security 4来保护REST服务。本文将重点讨论如何通过Login和Cookie来为REST API设置特定的安全配置。

2.在web.xml中配置Spring Security

Spring Security的体系结构是完全基于Servlet 过滤器的,因此,在处理HTTP请求的过程中,它会在Spring MVC之前。要记住这一点,因此首先要在应用程序的web.xml中配置一个过滤器

代码语言:javascript复制
<filter>
   <filter-name>springSecurityFilterChain</filter-name>
   <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
   <filter-name>springSecurityFilterChain</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

这个过滤器必须被命名为‘springSecurityFilterChain’,以匹配Spring容器中Spring Security创建的默认bean。

请注意,此处定义的过滤器不是实现安全逻辑的具体类,而是代理类org.springframework.web.filter.DelegatingFilterProxy,目的是将过滤器的方法委托给内部bean。通过代理模式不仅能够使servlet 容器中的filter同spring容器中的bean关联起来,并且能够避免与Spring耦合,这样就拥有了更好的灵活性。

在上面示例中,将过滤器的URL模式配置为/*,这样安全配置就有了选择,如果需要的话,也可以保护其他可能的映射。

3.使用XML配置Spring Security

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
   xmlns="http://www.springframework.org/schema/security"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:sec="http://www.springframework.org/schema/security"
   xsi:schemaLocation="
      http://www.springframework.org/schema/security 
      http://www.springframework.org/schema/security/spring-security-4.0.xsd
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">

   <http entry-point-ref="restAuthenticationEntryPoint">
      <intercept-url pattern="/api/admin/**" access="ROLE_ADMIN"/>
      <form-login
         authentication-success-handler-ref="mySuccessHandler"
         authentication-failure-handler-ref="myFailureHandler" />
      <logout />
   </http>

   <beans:bean id="mySuccessHandler"  class="org.rest.security.MySavedRequestAwareAuthenticationSuccessHandler"/>
   <beans:bean id="myFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
   <authentication-manager alias="authenticationManager">
      <authentication-provider>
         <user-service>
            <user name="temporary" password="temporary" authorities="ROLE_ADMIN"/>
            <user name="user" password="user" authorities="ROLE_USER"/>
         </user-service>
      </authentication-provider>
   </authentication-manager>
</beans:beans>

大部分配置都可以使用security命名空间来完成 – 为了启用命名空间,必须将schemaLocation属性配置为正确的 4.x XSD版本。Spring Security命名空间的引入可以简化我们的开发,并涵盖了大部分Spring Security常用的功能,同时仍然为原生bean提供一个调用钩子以适应更高级的场景。

3.1.<http>组件

<http>组件是HTTP安全配置的主要顶级组件。在当前的配置中,它只负责保护一个映射:/api/admin/*。请注意,映射是相对于web应用程序根上下文的,而不是REST Servlet;这是因为整个安全配置都存在于Spring根上下文中,而不是在Servlet的子上下文中。

3.2.认证入口点

在一个标准的web应用程序中,当客户端不经过身份认证就试图访问一个安全的资源时,身份认证过程可能会被自动触发——这通常是通过重定向到登录页面来实现的,这样用户就可以输入认证信息了。然而,对于REST Web服务而言,这种行为没有多大意义——身份认证只能通过请求正确的URI来完成,而如果用户没有经过身份认证,则所有其他请求都只需要简单的返回一个401 UNAUTHORIZED的状态码来表示失败即可。

Spring Security使用用入口点的概念来处理哪些映射需要自动触发身份认证过程——这是必需的配置,具体可以通过<http>组件的entry-point-ref属性来实现。请记住,在REST服务中,这个功能是没有意义的,新的自定义入口点被定义为在触发时简单返回401。

代码语言:javascript复制
@Component( "restAuthenticationEntryPoint" )
public class RestAuthenticationEntryPoint
  implements AuthenticationEntryPoint{

   @Override
   public void commence(
     HttpServletRequest request,
     HttpServletResponse response, 
     AuthenticationException authException) throws IOException {
      response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
   }

}

温馨提示: HTTP规范要求在没有WWW-Authenticate头时返回401状态码(Forbidden)——当然,如果需要的话,我们也可以手动设置这个值。

3.3.适合REST的<form-login>配置

对于REST API,有多种方法进行身份认证——Spring Security默认的提供了一个通过身份认证过滤器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter的form-login。

<form-login>组件将创建UsernamePasswordAuthenticationFilter过滤器,并允许在这个过滤器上配置我们自定义的认证成功处理器。因为命名空间提供的配置足够灵活,我们也可以通过使用<custom-filter>组件为FORM_LOGIN_FILTER注册一个过滤器来手动完成。

注意,对于一个标准的web应用程序来说,<http>组件的auto-config属性可以启用一些有用的安全配置。虽然这可能适合一些非常简单的配置,但它不适合并且也不应该用于REST API。

3.4.认证通过应该返回200而不是301

默认情况下,一个请求在身份认证成功后,<form-login>组件将使用 301 MOVED PERMANENTLY HTTP状态码进行响应;这在实际的表单登录上下文中是有意义的,因为它需要在登录后重定向。然而,对于一个基于REST的web服务,成功认证所需的响应应该是200 OK

这是通过在表单登录的过滤器中注入一个自定义认证成功处理器来完成的,以替换默认的配置。新的处理器实现类与默认的org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler完全相同,但有一个显著的区别——重定向逻辑被删除了:

代码语言:javascript复制
public class MySavedRequestAwareAuthenticationSuccessHandler 
  extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(
      HttpServletRequest request,
      HttpServletResponse response, 
      Authentication authentication) 
      throws ServletException, IOException {
        SavedRequest savedRequest
          = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            clearAuthenticationAttributes(request);
            return;
        }
        String targetUrlParam = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
          || (targetUrlParam != null
          && StringUtils.hasText(request.getParameter(targetUrlParam)))) {
            requestCache.removeRequest(request, response);
            clearAuthenticationAttributes(request);
            return;
        }

        clearAuthenticationAttributes(request);
    }

    public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
    }
}

3.5.认证失败应该返回401而不是302

类似地,我们配置了认证失败处理器——就像我们对成功处理器所做的那样。

幸运的是——在这种情况下,我们不需要为这个处理器定义一个新的类——标准实现类SimpleUrlAuthenticationFailureHandler已经做得很好了。

唯一的区别是,既然我们在XML配置中明确地定义了这一点——它不会从Spring中获得默认的defaultFailureUrl——因此就不会重定向了。

3.6.AuthenticationManager和Provider

身份认证过程使用一个基于内存的的provider来执行身份认证——这是为了简化配置,因为关于这些组件如何在生产环境使用的内容超出了本文的范围。

3.7.最后————针对REST服务的身份认证

现在,让我们看看如何使用REST API进行身份认证——登录的URL是/login——执行登录的 curl命令如下所示:

代码语言:javascript复制
curl -i -X POST -d username=user -d password=userPass
http://localhost:8080/spring-security-rest/login

这个请求将返回一个Cookie,之后的所有请求在调用REST服务时都会使用它。

我们可以使用 curl来进行身份认证,并将其接收到的cookie存储在文件中:

代码语言:javascript复制
curl -i -X POST -d username=user -d password=userPass -c /opt/cookies.txt 
http://localhost:8080/spring-security-rest/login

然后,我们可以使用存放在文件中的cookie来执行进一步的身份认证请求:

代码语言:javascript复制
curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt 
http://localhost:8080/spring-security-rest/api/foos

这个经过身份认证的请求将正确地收到一个200 OK的响应:

代码语言:javascript复制
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2013 20:31:13 GMT

[{"id":0,"name":"JbidXc"}]

4.使用Java配置来配置Spring Security

Java配置会类似下面的代码:

代码语言:javascript复制
@Configuration
@EnableWebSecurity
@ComponentScan("org.baeldung.security")
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private MySavedRequestAwareAuthenticationSuccessHandler
      authenticationSuccessHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth.inMemoryAuthentication()
          .withUser("temporary").password("temporary").roles("ADMIN")
          .and()
          .withUser("user").password("userPass").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        http
        .csrf().disable()
        .exceptionHandling()
        .authenticationEntryPoint(restAuthenticationEntryPoint)
        .and()
        .authorizeRequests()
        .antMatchers("/api/foos").authenticated()
        .and()
        .formLogin()
        .successHandler(authenticationSuccessHandler)
        .failureHandler(new SimpleUrlAuthenticationFailureHandler())
        .and()
        .logout();
    }

    @Bean
    public MySavedRequestAwareAuthenticationSuccessHandler mySuccessHandler(){
        return new MySavedRequestAwareAuthenticationSuccessHandler();
    }

    @Bean
    public SimpleUrlAuthenticationFailureHandler myFailureHandler(){
        return new SimpleUrlAuthenticationFailureHandler();
    }
}

温馨提示: Spring Security 4配置已经改变了XML配置中旧的默认值,使其与Java配置的默认值相同。

旧XML配置中的默认值 – Spring Security 4之前的版本:

  • loginProcessingUrl: /jspringsecurity_check
  • usernameParameter: j_username
  • passwordParameter: j_password

当前XML配置中的默认值:

  • loginProcessingUrl: /login
  • usernameParameter: username
  • passwordParameter: password

5.Maven和其它问题

web应用程序和REST服务所需的Spring核心依赖 已经详细讨论过了。对于安全性,我们需要添加:spring-security-web和spring-security-config——所有这些都已经在Spring Security的Maven依赖教程中进行了介绍。

值得密切关注Maven将解决较早的Spring依赖关系的方式——一旦安全组件被添加到pom文件,解决方案策略将开始引发问题 。 为了解决这个问题,需要对一些核心依赖项进行重写,以使它们保持在正确的版本。

6.总结

这篇文章介绍了使用Spring Security 4保护RESTful服务的基本安全配置及相关实现,讨论了web.xml、安全配置、认证过程的HTTP状态码和安全组件的Maven解析。

当然,与往常一样,完整的实现可以在Github上找到。

0 人点赞