背景
在項目使用了Spring Security之后,很多接口無法訪問了,從瀏覽器的網絡調試窗看到的是CORS的報錯和403的報錯
分析
我們先來看一下CORS是什么,和它很相似的CSRF是什么,在SpringSecurity中如何配置以及起的什么作用
CORS(Cross Origin Resource Sharing)
CORS跨域資源分享,是一種機制,通過在HTTP響應頭中加入特定字段限制不同域的資源請求
跨源HTTP請求的一個例子:運行在 https://domain-a.com 的 JavaScript 代碼使用 XMLHttpRequest 來發起一個到 https://domain-b.com/data.json 的請求
出於安全性,瀏覽器限制腳本內發起的跨源HTTP請求,而CORS就是用來允許跨源請求的
用法
用法: 在服務端的響應頭(Header)中增加以下字段
Header | 含義 | 例子 |
---|---|---|
Access-Control-Allow-Origin | 指定了允許訪問該資源的外域 URI | https://mozilla.org或者* |
Access-Control-Expose-Headers | 讓服務器把允許瀏覽器訪問的頭放入白名單 | X-My-Custom-Header, X-Another-Custom-Header |
Access-Control-Max-Age | 指定了preflight請求的結果能夠被緩存多久 |
|
Access-Control-Allow-Credentials | 當瀏覽器的 credentials 設置為 true 時是否允許瀏覽器讀取 response 的內容 | true |
Access-Control-Allow-Methods | 允許哪些方法 | GET、POST |
Access-Control-Allow-Methods | 允許哪些Header |
|
代碼非常簡單,有現成的兩種方式
SpringMVC
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CrossConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings( CorsRegistry registry ){
registry.addMapping( "/**" )
.allowedOrigins( "*" )
.allowedMethods( "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" )
.allowCredentials( true ).maxAge( 3600 ).allowedHeaders( "*" );
}
}
Spring Security
在Spring Security配置類中加入
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins( Collections.singletonList( "*" ) );
configuration.setAllowedMethods( Arrays.asList( "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" ) );
configuration.setAllowCredentials( true );
configuration.setAllowedHeaders( Collections.singletonList( "*" ) );
configuration.setMaxAge( 3600L );
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration( "/**", configuration );
return source;
}
注意
- 自己寫過濾器也行,總之把這些Header加入響應就行
- 上面代碼的策略非常寬松,可以適當限制域名保證安全
Spring Security和SpringMVC的CORS沖突
Spring Security是用訪問認證是過濾器來實現的
SpringMVC的CORS是用攔截器來實現的,參考SpringBoot中addCorsMappings配置跨域與攔截器互斥問題的原因研究
,其中寫入響應頭的類是org.springframework.web.cors.DefaultCorsProcessor
當存在Spring Security時,會存在加不上響應頭的現象,因為在過濾器階段可能因為認證不通過被拒絕了,所以當存在Spring Security的時候使用Spring Security的CORS用法就行
CSRF(Cross Site Request Forgery)
CSRF跨站請求偽造,是一種web攻擊手段,通過向服務器發送偽造請求,進行惡意行為的攻擊手段
舉個例子,你登錄了A網站,瀏覽器會記錄A網站的登錄Cookie,下次訪問就不會重新登錄了,而此時你訪問了B網站,網站的網頁攜帶一個惡意JS腳本,其中的內容是獲取或更改你A網站的信息,此時瀏覽器會自動的把Cookie帶上,A網站會認為這個請求是你本人的操作,CSRF就是用來防范這種攻擊的
如何防范
Spring Security的方式是在表單上面生成一個csrf_token, 提交表單的時候去驗證,因為第三方網站是沒有這個token的,所以提交不成功
無狀態應用
如這篇文章A Guide to CSRF Protection in Spring Security所說:
If our stateless API uses token-based authentication, like JWT, we don't need CSRF protection and we must disable it as we saw earlier.
使用JWT無狀態應用是不需要csrf保護的,因為JWT不使用cookie, 具體參考如何通過JWT防御CSRF
Spring Security關閉csrf
其中的csrf().disable()
@Override
protected void configure( HttpSecurity http ) throws Exception{
http
.cors()
.and()
.csrf()
.disable()
.sessionManagement()
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler( new SimpleAccessDeniedHandler() );
}
Set-Cookie問題
解決了cors的問題之后,再訪問Spring Security需要認證的URL時,還是出現403錯誤
看下save的請求頭,沒有帶上cookie
userinfo接口是登錄接口,響應頭包含了Set-Cookie,看起來沒有生效, 而且能確認是前端的問題
解決方法
通過修改前端axios配置, 增加withCredentials: true
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API2,
withCredentials: true,
transformResponse: [function (data) {
try {
return JSONBig.parse(data)
} catch (err) {
return data
}
}],
timeout: 20000 // request timeout
})