![](/img/trans.png)
[英]spring zuul gateway with oauth2 sso and angular 2 CORS issue
[英]CSRF issue with Spring + Angular 2 + Oauth2 + CORS
我正在開發基於 Spring 4.3 和 Angular (TypeScript) 4.3 的客戶端 - 服務器應用程序,在 CORS 場景中,在生產中,服務器和客戶端位於不同的域中。 客戶端通過 http 請求請求 REST 服務器 API。
1. REST 和 OAUTH 配置:
服務器公開 REST API:
@RestController
@RequestMapping("/my-api")
public class MyRestController{
@RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity<Boolean> test()
{
return new ResponseEntity<Boolean>(true, HttpStatus.OK);
}
}
受 Oauth2 保護,如 spring 文檔中所述。 顯然,我修改了上述內容以適合我的應用程序。 一切正常:我能夠通過 refresh_token 和 access_token 使用 Oauth2 保護/my-api/test
。 Oauth2 沒有問題。
2. CORS 配置:
由於服務器相對於客戶端位於單獨的域上(服務器: 10.0.0.143:8080
: 10.0.0.143:8080
,客戶端: localhost:4200
,正如我現在正在開發的那樣),我需要在服務器端啟用 CORS:
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
@Autowired
SimpleCorsFilter simpleCorsFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.addFilterAfter(simpleCorsFilter, CorsFilter.class)
.csrf().disable() // notice that now csrf is disabled
... (the rest of http security configuration follows)...
}
}
其中 SimpleCorsFilter 添加我需要的標題:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter extends OncePerRequestFilter {
public SimpleCorsFilter() {
}
@Override
public void destroy() {
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.addHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH,OPTIONS");
response.addHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Allow-Headers", "MyCustomHeader, Authorization, X-XSRF-TOKEN");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, response);
}
}
}
現在,如果我使用 angular2 發出 http get 或 post 請求,例如:
callPostMethod(tokenData) {
const url = 'my-api.domain.com/my-api';
const pars = new HttpParams();
const body = null;
let hds = new HttpHeaders()
.append('Authorization', 'Bearer ' + tokenData.access_token)
.append('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post <Installation> (url, body, {
params : pars,
headers: hds,
withCredentials: true
});
}
一切正常。 所以即使是 CORS 配置似乎也可以。
3.CSRF配置:
如果我現在像這樣在 Spring 中啟用 CSRF 配置:
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
@Autowired
SimpleCorsFilter simpleCorsFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.addFilterAfter(simpleCorsFilter, CorsFilter.class)
.csrf().csrfTokenRepository(getCsrfTokenRepository()) // notice that csrf is now enabled
... (the rest of http security configuration follows)...
}
@Bean
@Autowired
// used only because I want to setCookiePath to /, otherwise I can simply use
// http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
public CsrfTokenRepository getCsrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
//tokenRepository.setHeaderName("X-XSRF-TOKEN"); // has already this name --> comment
tokenRepository.setCookiePath("/");
return tokenRepository;
}
}
在第一個 POST 請求中,它給了我 403 錯誤:
"Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'."
為什么會這樣(據我所知..)?
通過探索 CSRF 如何工作的機制,我注意到 Spring 正確生成了一個名為XSRF-TOKEN的 cookie,該 cookie 設置在響應 cookie 中(通過使用 Chrome 檢查請求可見,響應頭下的 Set-Cookie)。
接下來應該發生的是,angular 在執行第一個 POST 請求時,應該讀取從 Spring 收到的 cookie 並生成一個名為X-XSRF-TOKEN的請求 Header,其值等於 cookie 的值。
如果我檢查失敗的 POST 請求的 Header,我會看到沒有 X-XSRF-TOKEN,就像 angular 沒有按照它的 CSRF 規范做它應該做的一樣,請看圖片:
查看 xsrf (/angular/angular/blob/4.3.5/packages/common/http/src/xsrf.ts) 的角度實現,您可以看到在 HttpXsrfInterceptor 中,如果目標 URL 以http
開頭,則不會添加 csrf 標頭(以下是 angular xsrf.ts 源代碼復制和粘貼):
/**
* `HttpInterceptor` which adds an XSRF token to eligible outgoing requests.
*/
@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
constructor(
private tokenService: HttpXsrfTokenExtractor,
@Inject(XSRF_HEADER_NAME) private headerName: string) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const lcUrl = req.url.toLowerCase();
// Skip both non-mutating requests and absolute URLs.
// Non-mutating requests don't require a token, and absolute URLs require special handling
// anyway as the cookie set
// on our origin is not the same as the token expected by another origin.
if (req.method === 'GET' || req.method === 'HEAD' || lcUrl.startsWith('http://') ||
lcUrl.startsWith('https://')) {
return next.handle(req);
}
const token = this.tokenService.getToken();
// Be careful not to overwrite an existing header of the same name.
if (token !== null && !req.headers.has(this.headerName)) {
req = req.clone({headers: req.headers.set(this.headerName, token)});
}
return next.handle(req);
}
}
如果我自己添加 X-XSRF-TOKEN 標頭怎么辦?
由於 angular 沒有,我嘗試通過如下修改請求標頭將標頭附加到請求中:
const hds = new HttpHeaders()
.append('Authorization', 'Bearer ' + tokenData.access_token)
.append('Content-Type', 'application/x-www-form-urlencoded');
if (this.getCookie('XSRF-TOKEN') !== undefined) {
hds = hds.append('X-XSRF-TOKEN', this.getCookie('XSRF-TOKEN'));
}
其中this.getCookie('XSRF-TOKEN')
是一種使用'angular2-cookie/services'
讀取瀏覽器 cookie 的方法,但this.getCookie('XSRF-TOKEN')
返回 null。
為什么? 據我了解, javascript cookie 檢索失敗,因為即使 Spring 在響應中返回 XSRF-TOKEN cookie,它也不會在瀏覽器中設置,因為它與客戶端位於不同的域中(服務器域: 10.0.0.143:8080
,客戶端域: localhost:4200
) 。
如果服務器也在 localhost 上運行,即使在不同的端口上(即服務器域: localhost:8080
,客戶端域: localhost:4200
),響應中從 spring 服務器設置的 cookie 在瀏覽器中正確設置,因此可以使用this.getCookie('XSRF-TOKEN')
方法通過 angular 檢索。
看看我的意思是觀察下圖中兩個不同調用的結果:
localhost 和跨域 POST 請求,chrome 檢查
如果我是對的,這與域localhost:4200
無法通過 javascript 讀取域10.0.0.143:8080
的 cookie 的事實是一致的。 請注意,選項withCredentials = true
允許 cookie 從服務器流向客戶端,但只是透明的,這意味着它們不能通過 javascript 修改。 只有服務器可以讀取和寫入自己域的 cookie。 甚至客戶端也可以,但前提是它與服務器在同一個域中運行(我說得對嗎?)。 如果服務器和客戶端都在同一個域上運行,即使在不同的端口上,手動添加頭也是有效的(但在生產服務器和客戶端位於不同的域中,所以這不是解決方案)。
所以問題是
目前的選項是:
如果我正確理解了該機制,如果客戶端和服務器位於不同的域上,Spring 和 Angular 標准 CSRF 令牌交換機制將無法工作,因為 (1) angular 實現不支持它和 (2) javascript 無法訪問 XSRF-TOKEN cookie因為后者在服務器的域上。 如果是這種情況,我可以在沒有 CSRF 的情況下僅依賴無狀態 oauth2 refresh_token 和 access_token 安全性嗎? 從安全的角度來看可以嗎?
或者,另一方面,我遺漏了一些東西,還有另一個我沒有看到的原因(這就是我問你的原因,親愛的開發人員)實際上 CSRF 和 CORS 應該可以工作,所以這是我的代碼錯誤或錯誤缺少一些東西。
鑒於這種情況,你能告訴我你會怎么做嗎? 我的代碼中是否有一些錯誤導致跨域場景中的 CSRF 不起作用? 如果您需要更多信息來詢問我的問題,請告訴我。
抱歉有點長,但我認為最好解釋一下完整的解決方案,以便讓您了解我面臨的問題。 而且,此外,我編寫和解釋的代碼可能對某些人有用。
最好的問候, 吉安卡羅
廣告 2. 您的代碼完全有效並且您正確設置了所有內容。 Spring 中的 CSRF 保護設計為前端與后端在同一域中。 由於 Angular 無法訪問 CSRF 數據,因此顯然無法在修改請求中設置它。 如果沒有在常規標頭(不是 cookie)的服務器過濾器中設置它們,就無法訪問它們。
廣告 1. JWT 令牌的安全性足夠好,因為大公司成功使用了它們。 但是,請記住,令牌本身應使用 RSA 密鑰(而不是更簡單的 MAC 密鑰)進行簽名,並且所有通信都必須通過安全連接 (https/ssl)。 使用刷新令牌總是會略微降低安全性。 業務應用程序通常會忽略它們。 一般受眾應用程序必須安全地存儲它們,但仍可以選擇在濫用時放棄它們的有效性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.