Spring MVCとRestTemplateによるCorsProxyの実現
CORS PROXYとは
ドメイン間の問題は皆さんご存知のように、ajaxリクエストは別のドメイン名の下のインタフェースを直接呼び出すことはできません.jsonpは一定の問題を解決することができますが、Post、PUT、DELETEなどの高度な機能のサポートにはどうしようもありません.
この問題を解決するために、高級ブラウザではCORSがサポートされ始めました.CORSはheadersで関連パラメータを定義し、ブラウザの私のインタフェースが外部のサイトに要求されることを許可するかどうか、どのMethodを許可するかなどを教えてくれました.
具体的な使い方については、関連ドキュメントを参照してください.https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
CORSがあって、次の問題がまた来て、CORSはサービス側に関連するheadersを加えなければならないので、サードパーティのインタフェースがCORSを有効にしていないのはどうしますか?
彼らが実現に間に合わないのではなく、安全性のために実現したくないのです.CORSには一定の危険性があるからです.http://www.freebuf.com/articles/web/18493.html
CORSには定義がある
ハッカーはXSSを利用して関連攻撃を行う可能性が高い.
だからどうやってこのような第三者のインターフェースに対処しましたか?
やっとCORS Proxyを引き出しました!
CORS Proxyはクライアントとサードパーティサーバの間のエージェントサーバであり、このサーバは自分でしか使用できません(
CORS Proxy内部では、Http Clientでサードパーティサーバを要求します.
注意!コードで書かれたHttp Clientは、server側の制限ではなく、ブラウザが追加したセキュリティ保護措置であるため、ドメイン間の問題は存在しません.
CORS PROXY実現原理
CORS Proxyの原理は実は簡単で、主に3つのことをしています.認証 転送要求 CORS関連headers付き Javaでどうやって実現したの?
Spring MVCとSpringのRestTemplateを用いてCORS Proxyを実現した.
コードは複雑ではありません.ここではControllerです.もう一つのFilterがあります.
コードの中の
いくつかの注意事項
この実現は難しくないが、その中の1つの部分は大きな穴だ.
コーディングの問題
最初は私のCORS Proxyで受けていた
もう一つは
どうやって解決しますか?呼び出し
Transfer-Encoding
最初はserver側から戻ってきたbodyとheadersをそのままclientに渡しましたが、clientはずっと接続を中断していました!データが全く届かない.
直感はheadersの中のいくつかに問題があると思います.そこで排除法で一つ一つ試してみると、問題は
これはブロック転送符号化だったのか、なぜこのヘッダを加えると問題があったのか.
私のエージェントはすでにserver側とすべてのデータを転送し終わったので、私はすべてのデータをclientに返しましたが、エージェントはまたclientに自分がブロック転送だと言いました...clientは理解できません...
最後に私はまた1つ
Content-Length
クライアントとproxyの間にgzipがあり、serverとproxyの間にgzipがないシーンがあります.
これはproxyがクライアントに伝える
どうやって解決しますか?同様に上の
Access-Control-Allow-Origin
CORS規格にはいくつかのヘッダがあり、そのうちの1つは
私の上の
元の住所には
それから
ワイルドカードかドメイン名が1つしか許可されていないということです!
私は2回プラスして、解析されました
複数のドメイン名があればどうしますか?それはこのヘッダを動的に生成するしかありません.
しかし、セキュリティの観点から、混用せずに独自のCorsPorxyを独立して導入することを強くお勧めします.
リダイレクトの問題
この話題の設計のものは比較的に多くて、すでに自作の文章:RestTemplateの自動リダイレクト機能を無効にします
後ろにはどんな穴がありますか?私たちはまだ出会っていませんが、多くのものを配置に書いて、将来問題があることに気づいたら配置を変更しました.実現論理はそれだけで、配置できるところもそれだけで、万変はその宗から離れない.
本作品はDozerによって作成され、知識共有署名-非商業的使用4.0国際ライセンス契約を採用してライセンスされている.
ドメイン間の問題は皆さんご存知のように、ajaxリクエストは別のドメイン名の下のインタフェースを直接呼び出すことはできません.jsonpは一定の問題を解決することができますが、Post、PUT、DELETEなどの高度な機能のサポートにはどうしようもありません.
この問題を解決するために、高級ブラウザではCORSがサポートされ始めました.CORSはheadersで関連パラメータを定義し、ブラウザの私のインタフェースが外部のサイトに要求されることを許可するかどうか、どのMethodを許可するかなどを教えてくれました.
具体的な使い方については、関連ドキュメントを参照してください.https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
CORSがあって、次の問題がまた来て、CORSはサービス側に関連するheadersを加えなければならないので、サードパーティのインタフェースがCORSを有効にしていないのはどうしますか?
彼らが実現に間に合わないのではなく、安全性のために実現したくないのです.CORSには一定の危険性があるからです.http://www.freebuf.com/articles/web/18493.html
CORSには定義がある
Access-Control-Allow-Origin
ドメイン間リクエストが許可されているソースを示すために使用され、上記のようなサードパーティ共通インタフェースがCORSを開放している場合、ソースは不確定であるため、ここですべてのソースを許可するように構成すると非常に危険である.ハッカーはXSSを利用して関連攻撃を行う可能性が高い.
だからどうやってこのような第三者のインターフェースに対処しましたか?
やっとCORS Proxyを引き出しました!
CORS Proxyはクライアントとサードパーティサーバの間のエージェントサーバであり、このサーバは自分でしか使用できません(
Access-Control-Allow-Origin
クライアントのアドレスに設定します).CORS Proxy内部では、Http Clientでサードパーティサーバを要求します.
注意!コードで書かれたHttp Clientは、server側の制限ではなく、ブラウザが追加したセキュリティ保護措置であるため、ドメイン間の問題は存在しません.
CORS PROXY実現原理
CORS Proxyの原理は実は簡単で、主に3つのことをしています.
Spring MVCとSpringのRestTemplateを用いてCORS Proxyを実現した.
@Controller
@RequestMapping(value = "/corsproxy")
public class CorsProxyController {
private Logger logger = LoggerFactory.getLogger(getClass());
private RestTemplate restTemplate;
private HeaderFilter headerFilter;
private TargetUrlFilter targetUrlFilter;
private final String CORS_PREFIX = "corsproxy/";
private final String HTTP_PREFIX = "http/";
private final String HTTPS_PREFIX = "https/";
@RequestMapping(value = "/**")
public ResponseEntity<byte[]> proxy(HttpServletRequest request, @RequestBody byte[] body, @RequestHeader MultiValueMap headers) throws UnsupportedEncodingException {
String url = request.getRequestURI();
String queryString = request.getQueryString();
if (queryString != null && queryString != "") {
url = url + "?" + queryString;
}
String targetUrl = getTargetUrl(url);
if (!targetUrlFilter.checkUrl(targetUrl)) {
return new ResponseEntity<byte[]>(HttpStatus.FORBIDDEN);
}
ResponseEntity<byte[]> result = null;
try {
result = restTemplate.exchange(new URI(targetUrl), HttpMethod.valueOf(request.getMethod()), new HttpEntity<byte[]>(body, headers), byte[].class);
} catch (HttpClientErrorException exp) {
return new ResponseEntity<byte[]>(exp.getResponseBodyAsByteArray(), getResponseHeaders(exp.getResponseHeaders()), exp.getStatusCode());
} catch (HttpServerErrorException exp) {
return new ResponseEntity<byte[]>(exp.getResponseBodyAsByteArray(), getResponseHeaders(exp.getResponseHeaders()), exp.getStatusCode());
} catch (Exception exp) {
return new ResponseEntity<byte[]>(exp.getMessage().getBytes("utf-8"), getResponseHeaders(new HttpHeaders()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<byte[]>(result.getBody(), getResponseHeaders(result.getHeaders()), result.getStatusCode());
}
@Resource(name = "restTemplate")
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Resource(name = "headerFilter")
public void setHeaderFilter(HeaderFilter headerFilter) {
this.headerFilter = headerFilter;
}
@Resource(name = "targetUrlFilter")
public void setTargetUrlFilter(TargetUrlFilter targetUrlFilter) {
this.targetUrlFilter = targetUrlFilter;
}
private String getTargetUrl(String url) {
String targetUrl = url.substring(url.indexOf(CORS_PREFIX) + CORS_PREFIX.length());
if (targetUrl.indexOf(HTTP_PREFIX) == 0) {
targetUrl = "http://" + targetUrl.substring(HTTP_PREFIX.length());
} else if (targetUrl.indexOf(HTTPS_PREFIX) == 0) {
targetUrl = "https://" + targetUrl.substring(HTTPS_PREFIX.length());
}
return targetUrl;
}
private HttpHeaders getResponseHeaders(HttpHeaders originHeaders) {
HttpHeaders header = new HttpHeaders();
for (Entry> item : originHeaders.entrySet()) {
if (headerFilter.needRemoveHeader(item.getKey(), item.getValue().toString())) {
continue;
}
header.put(item.getKey(), item.getValue());
}
return header;
}
}
コードは複雑ではありません.ここではControllerです.もう一つのFilterがあります.
public class CorsFilter extends OncePerRequestFilter implements Filter{
private HeaderHelper headerHelper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
for(Map.Entry extends String, ? extends List<String>> header: headerHelper.getHeadersMap().entrySet()){
Joiner joiner = Joiner.on("; ").skipNulls();
String value = joiner.join(header.getValue());
response.addHeader(header.getKey(),value);
}
filterChain.doFilter(request, response);
}
@Resource(name = "headerHelper")
public void setHeaderHelper(HeaderHelper headerHelper) {
this.headerHelper = headerHelper;
}
}
コードの中の
HeaderHelper
、HeaderFilter
とTargetUrlFilter
あまり論理的ではなく、構成を読み取っただけです.いくつかの注意事項
この実現は難しくないが、その中の1つの部分は大きな穴だ.
コーディングの問題
最初は私のCORS Proxyで受けていた
RequestBody
はいString
で、RestTemplate
リクエストの戻り値もString
でしたが、後になってその中に多くの問題があることに気づきました.Clientとserverのコード仕様は必ずしも標準ではありませんが、実はあなたはエージェントサーバとして、コードを行う必要はありません.clientがあなたに渡したものは何なのか、そのままserverに伝えればいいので、私たちが作成するときはすべて使いましたbyte[]
、これからは問題ありません.もう一つは
GET
のQueryString
、ここは穴だらけ!中国語であればRequest
から得られるものは符号化されたものであり、自動的に中国語に復号されることはない.そしてRestTemplate
自動でエンコードされるので、クライアントが受け取ったのは2回のエンコードの内容で、1回だけ復号すると、中国語は得られません.どうやって解決しますか?呼び出し
RestTemplate
の場合はやめましょうString
タイプのurlを渡すのではなく、1つURI
オブジェクトを渡すことでRestTemplate
自動符号化されなくなります.Transfer-Encoding
最初はserver側から戻ってきたbodyとheadersをそのままclientに渡しましたが、clientはずっと接続を中断していました!データが全く届かない.
直感はheadersの中のいくつかに問題があると思います.そこで排除法で一つ一つ試してみると、問題は
Transfer-Encoding
にある.これはブロック転送符号化だったのか、なぜこのヘッダを加えると問題があったのか.
私のエージェントはすでにserver側とすべてのデータを転送し終わったので、私はすべてのデータをclientに返しましたが、エージェントはまたclientに自分がブロック転送だと言いました...clientは理解できません...
最後に私はまた1つ
HeaderFilter
を書いて、それからいくつかの伝送する必要のないheaderを濾過しました.Content-Length
Content-Length
穴でもあるのですが、なぜでしょうか?サーバ側から伝わってきたのでContent-Length
クライアントに直接伝えることはできません.クライアントとproxyの間にgzipがあり、serverとproxyの間にgzipがないシーンがあります.
これはproxyがクライアントに伝える
Content-Length
圧縮前の長さで、問題が発生します.どうやって解決しますか?同様に上の
HeaderFilter
を使うと、フィルタリングしてproxyがいるサービス側が自動的にResponse
加えてContent-Length
の、手動で指定する必要はありません.Access-Control-Allow-Origin
CORS規格にはいくつかのヘッダがあり、そのうちの1つは
Access-Control-Allow-Origin
で、どのドメイン名でドメインをまたいでアドレスを要求できるかを表しています.私の上の
HeaderHelper
自動でこのヘッドを付けますが、後で奇抜なcaseに遭遇しました!元の住所には
Access-Control-Allow-Origin
が付いていて、それから私のHeaderHelper
もう一つ追加します.それから
Access-Control-Allow-Origin
このヘッドはとても穴のお父さんで、それは*
であってもよいし、http://www.dozer.cc
であってもよいが、それはhttp://www.dozer.cc, http://www.baidu.com
であってはならない.ワイルドカードかドメイン名が1つしか許可されていないということです!
私は2回プラスして、解析されました
\*,\*
、それからブラウザは認識していません...複数のドメイン名があればどうしますか?それはこのヘッダを動的に生成するしかありません.
しかし、セキュリティの観点から、混用せずに独自のCorsPorxyを独立して導入することを強くお勧めします.
リダイレクトの問題
この話題の設計のものは比較的に多くて、すでに自作の文章:RestTemplateの自動リダイレクト機能を無効にします
後ろにはどんな穴がありますか?私たちはまだ出会っていませんが、多くのものを配置に書いて、将来問題があることに気づいたら配置を変更しました.実現論理はそれだけで、配置できるところもそれだけで、万変はその宗から離れない.
本作品はDozerによって作成され、知識共有署名-非商業的使用4.0国際ライセンス契約を採用してライセンスされている.