在 RESTful API 中使用 Spring Security

介紹

在篇文章中,咱們將學習如何使用 Spring 和 Spring Security 5 提供更安全的 RESTful API。
咱們將使用 Java 配置來設置安全性,並將使用登陸和 Cookie 方法進行身份驗證。java

啓用Spring Security

Spring Security 的體系結構徹底基於 Servlet 過濾器。
註冊 Spring Security 過濾器的最簡單選擇是添加 @EnableWebSecurity 註解到咱們的 config 類:web

@Config
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
 
    // ...
}

對於非 Spring Boot 應用,咱們能夠擴展 AbstractSecurityWebApplicationInitializer 並在其構造函數中傳遞 config 類:spring

public class SecurityWebApplicationInitializer 
  extends AbstractSecurityWebApplicationInitializer {
 
    public SecurityWebApplicationInitializer() {
        super(SecurityJavaConfig.class);
    }
}

或者咱們能夠在應用的 web.xml 中聲明它:shell

<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 Security 建立的默認 bean。
注意,定義的過濾器不是實現安全邏輯的實際類。相反,它是一個委託 filterproxy,將篩選器的方法委託給內部 bean。這樣作是爲了讓目標 bean 仍然可以從 Spring 上下文的生命週期和靈活性中受益。json

Spring Security Java配置

咱們能夠徹底在 Java 類中進行安全配置,方法是建立一個擴展了 WebSecurityConfigurerAdapter 的配置類,並用 @EnableWebSecurity 對其進行註釋:api

@Configuration
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
 
    // ...
}

如今,讓咱們在 SecurityJavaConfig 中建立不一樣角色的用戶,咱們將使用這些用戶來驗證咱們的 API 端點:安全

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN")
        .and()
        .withUser("user").password(encoder().encode("userPass")).roles("USER");
}
 
@Bean
public PasswordEncoder  encoder() {
    return new BCryptPasswordEncoder();
}

接下來,讓咱們爲咱們的 API 端點配置安全性:cookie

@Override
protected void configure(HttpSecurity http) throws Exception { 
    http
    .csrf().disable()
    .exceptionHandling()
    .authenticationEntryPoint(restAuthenticationEntryPoint)
    .and()
    .authorizeRequests()
    .antMatchers("/api/foos").authenticated()
    .antMatchers("/api/admin/**").hasRole("ADMIN")
    .and()
    .formLogin()
    .successHandler(mySuccessHandler)
    .failureHandler(myFailureHandler)
    .and()
    .logout();
}

Http

在咱們的代碼實現中,咱們使用 antMatchers 建立安全的映射 /api/foos 和 /api/admin/**。
任何通過身份驗證的用戶均可以訪問 /api/foos。另外一方面,/api/admin/** 只能被 admin 角色用戶訪問。
在標準的 web 應用程序中,當未經身份驗證的客戶機試圖訪問受保護的資源時,身份驗證過程可能會自動觸發。此過程一般重定向到登陸頁面,以便用戶能夠輸入憑據。
然而,對於 REST Web 服務,這種行爲沒有多大意義。咱們應該可以僅經過對正確URI的請求進行身份驗證,若是用戶沒有通過身份驗證,則全部請求都應該失敗,並帶有401未受權狀態碼。
Spring Security 使用 Entry Point 的概念處理身份驗證過程的自動觸發 ——這是配置中必需的一部分,能夠經過 authenticationEntryPoint 方法注入。
在觸發時簡單地返回 401:app

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

用於 REST 的登陸表單

有多種方法能夠對 REST API 進行身份驗證。 Spring Security 提供的默認值之一是表單登陸,它使用身份驗證處理過濾器 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter。
formLogin 元素將建立這個過濾器,並提供額外的方法 successHandler 和 failureHandler 來分別設置咱們的自定義身份驗證成功和失敗處理程序。curl

身份驗證應該返回 200 而不是 301

默認狀況下,表單登陸將使用 301 狀態碼來回答成功的身份驗證請求,這在登陸後須要重定向的實際登陸表單的上下文中是有意義的。
然而,對於 RESTful web 服務,成功驗證所需的響應應該是 200 OK。
爲此,咱們在表單登陸篩選器中注入一個自定義身份驗證成功處理程序,以替換默認的處理程序。新的處理程序實現與默認的 org.springframe .security.web.authentication 徹底相同的登陸。SavedRequestAwareAuthenticationSuccessHandler 有一個明顯的區別-它刪除了重定向邏輯:

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;
    }
}

身份驗證管理器和提供程序

身份驗證過程使用內存中的提供者來執行身份驗證。
咱們建立了兩個用戶,即具備 USER 角色的 user 和具備 ADMIN 角色的 admin。

最後—針對正在運行的 REST 服務進行身份驗證

如今,讓咱們看看如何針對不一樣的用戶針對REST API進行身份驗證。
登陸的 URL 爲 /login,一個簡單的 curl 命令,用於對名爲 user 和密碼 userPass 的用戶執行登陸。

curl -i -X POST -d username=user -d password=userPass
http://localhost:8080/spring-security-rest/login

此請求將返回 Cookie,咱們能夠將其用於針對 REST 服務的任何後續請求。
咱們可使用 curl 進行認證,並將它接收到的 cookie 保存在一個文件中

curl -i -X POST -d username=user -d password=userPass -c /opt/cookies.txt 
http://localhost:8080/spring-security-rest/login

而後咱們可使用文件中的 cookie 來作進一步的認證請求:

curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt 
http://localhost:8080/spring-security-rest/api/foos

因爲用戶能夠訪問 /api/foos/* 端點,這個通過身份驗證的請求將正確地獲得一個 200 OK

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":"qulingfeng"}]

相似地,對於 admin 用戶,咱們可使用 curl 進行認證:

curl -i -X POST -d username=admin -d password=adminPass -c /opt/cookies.txt 
http://localhost:8080/spring-security-rest/login

而後更新 cookies 訪問管理端點 /api/admin/* :

curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt 
http://localhost:8080/spring-security-rest/api/admin/x

因爲管理用戶能夠訪問端點 /api/admin/*,全部響應成功:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 5
Date: Mon, 15 Oct 2018 17:16:39 GMT
 
Hello

XML 配置

咱們也能夠用 XML 代替 Java 配置來完成以上全部的安全配置:

<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="admin" password="adminPass" authorities="ROLE_ADMIN"/>
            <user name="user" password="userPass" authorities="ROLE_USER"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

結束語

在本教程中,咱們介紹了使用 Spring security 5 實現 RESTful 服務的基本安全配置和實現。
咱們學習瞭如何徹底經過Java配置爲 REST API 進行安全配置,並研究了其 web.xml 配置替代方案。
接下來,咱們討論瞭如何爲受保護的應用程序建立用戶和角色,以及如何將這些用戶映射到應用程序的特定端點。
最後,咱們還研究瞭如何建立自定義身份驗證入口點和自定義成功處理程序,以便在控制安全性方面爲應用程序提供更好的靈活性。

歡迎關注個人公衆號:曲翎風,得到獨家整理的學習資源和平常乾貨推送。
若是您對個人專題內容感興趣,也能夠關注個人博客: sagowiec.com
相關文章
相關標籤/搜索