JSONP

造成跨域的两种策略

浏览器的同源策略会导致跨域,这里同源策略又分为以下两种:

  • DOM同源策略:禁止对不同源页面DOM进行操作(这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的)

  • XMLHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

为什么要有跨域限制

AJAX同源策略主要用来防止CSRF攻击,试想如果没有AJAX同源策略,我们发起的每一次HTTP请求都会带上请求地址对应的cookie(用户标识),那就相当危险了。因为一旦cookie泄露,攻击者就可以模拟用户进行一些非法操作了,所以,跨域限制主要是为了安全考虑。

跨域的解决方式

CORS

CORS(Cross-Origin Resource Sharing)即跨域资源共享,是一个W3C标准,大体流程:

  • 对于客户端,我们还是正常使用xhr对象发送ajax请求,唯一需要注意的是,我们需要设置xhr的withCredentials属性为true,否则,cookie是不发送的。
  • 对于服务器端,需要在响应头中设置如下两个字段:
    1
    2
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Origin: http://www.domain.com

这样,我们就可以跨域请求接口了。

JSONP

我们知道,<script>标签是不受同源策略的限制的,它可以载入任意地方的js脚本而并不要求同源。JSONP正是利用了这一点,通过动态添加<script>标签来实现跨域请求。

JSONP的核心就是:允许用户传递一个callback参数给服务端,服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

var callbackName, overwritten, responseContainer,
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
"url" :
typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
);

// Handle iff the expected data type is "jsonp" or we have a parameter to set
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

// Get callback name, remembering preexisting value associated with it
callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback;

// Insert callback into url or form data
if ( jsonProp ) {
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
} else if ( s.jsonp !== false ) {
s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
}

// Use data converter to retrieve json after script execution
s.converters["script json"] = function() {
if ( !responseContainer ) {
jQuery.error( callbackName + " was not called" );
}
return responseContainer[ 0 ];
};

// force json dataType
s.dataTypes[ 0 ] = "json";

// Install callback
overwritten = window[ callbackName ];
window[ callbackName ] = function() {
responseContainer = arguments;
};

// Clean-up function (fires after converters)
jqXHR.always(function() {
// Restore preexisting value
window[ callbackName ] = overwritten;

// Save back as free
if ( s[ callbackName ] ) {
// make sure that re-using the options doesn't screw things around
s.jsonpCallback = originalSettings.jsonpCallback;

// save the callback name for future use
oldCallbacks.push( callbackName );
}

// Call if it was a function and we have a response
if ( responseContainer && jQuery.isFunction( overwritten ) ) {
overwritten( responseContainer[ 0 ] );
}

responseContainer = overwritten = undefined;
});

// Delegate to script
return "script";
}
});

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {
// This transport only deals with cross domain requests
if ( s.crossDomain ) {
var script, callback;
return {
send: function( _, complete ) {
script = jQuery("<script>").prop({
async: true,
charset: s.scriptCharset,
src: s.url
}).on(
"load error",
callback = function( evt ) {
script.remove();
callback = null;
if ( evt ) {
complete( evt.type === "error" ? 404 : 200, evt.type );
}
}
);
document.head.appendChild( script[ 0 ] );
},
abort: function() {
if ( callback ) {
callback();
}
}
};
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* <script src="url?jsonpCallback=callbackFunction"></script>
*/
@RestController
@RequestMapping("/account")
public class AccountResource {

// @GetMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// public String getAccount(@RequestParam String jsonpCallback) {
// Subject subject = subjectService.getSubject();
// String json = JsonUtils.toJson(subject);
// return String.format("%s(%s)", jsonpCallback, json);
// }

@GetMapping
public ResponseEntity<MappingJacksonValue> getAccount(@RequestParam String jsonpCallback) {
Subject subject = subjectService.getSubject();
MappingJacksonValue jacksonValue = new MappingJacksonValue(subject);
jacksonValue.setJsonpFunction(jsonpCallback);
return ResponseEntity.ok(jacksonValue);
}

}