Spring Security怎么实现的跨域?

2023-02-05 0 320

序言

假如你的工程项目采用了服务工程项目交换机, 比如说Spring Gateway 听我的, 布吕马难题在服务工程项目交换机化解, 假如你的工程项目没采用服务工程项目交换机再考量在Spring Security化解布吕马难题好嘛? (总之我这句话须要依照你的工程项目前述情形考量)

再者为甚么我要那么说, 我就试试你, Spring Security呢冷却系统? 那Spring Gateway也呢冷却系统? 那谁的冷却系统先继续执行? 谁的冷却系统后继续执行? 假如 Spring Security加进了cors布吕马, 但被Spring Gateway修正了咋办? 但若又怎样?

你要考量, 假如Spring Security或是Spring Gateway再次出现了极度与否会绕开你想像中要继续执行的但前述仍未继续执行的冷却系统

读懂冷却系统较好用, 但次序和继续执行情形须要轻微愤慨

主要就讲了甚么?

第一集将如是说布吕马难题化解方案, 当中有 Spring 布吕马 和 Spring Security布吕马难题

此基础

甚么是布吕马?

应用程序从两个搜索引擎的页面去允诺另两个搜索引擎的天然资源时,搜索引擎、路由器、协定下述相同,都是布吕马 搜索引擎:

主搜索引擎相同 www.baidu.com/index.html –>www.sina.com/test.js

子搜索引擎相同

www.666.baidu.com/index.html –>www.555.baidu.com/test.js

搜索引擎和搜索引擎ip

www.baidu.com/index.html –>

http://180.149.132.47/test.js

路由器:  

www.baidu.com:8080/index.html–…

www.baidu.com:8081/test.js

协定:  

www.baidu.com:8080/index.html–…

www.baidu.com:8080/test.js

备注:   1、路由器和协定的相同,只能通过后台来化解

2、localhost127.0.0.1虽然都指向本机,但也属于布吕马

布吕马难题的化解方案都有哪些?

1、客户端应用程序解除布吕马限制(理论上可以但不现实)

2、发送JSONP允诺替代XHR允诺(并不能适用所有的允诺方式,不推荐)

3、修正服务工程项目器端(包括HTTP服务工程项目器和应用服务工程项目器)(推荐

JSONP

JSONP(JSON with Padding)JSON的一种补充采用方式,不是官方协定

JSONP 利用了前端标签的属性src可以访问第三方网站允诺并继续执行的漏洞, 访问第三方网站, 并返回第三方网站的内容

所以站在用户网站来说, 你须要提供:

两个带着 src 属性的标签, 比如说 <script> <img>等, 里面填入布吕马服务工程项目端的允诺地址<script src=“http://localhost:8080/jsonp?callback=callback”></script>两个带着参数的回调函数, 该参数用户接收布吕马服务工程项目端的数据function callback(data) { console.log(data) alert(data) }想办法告知布吕马服务工程项目端, 你的回调函数名(一般放在 src 末尾的 callback属性上, 比如说:http://xxx.com?callback=回调函数名)?callback=callback // 将回调函数的名字传递给布吕马服务工程项目端

布吕马服务工程项目端呢?

须要构建两个controller或是说构建两个允诺, 该允诺对应着用户网站的src 地址@GetMapping(“jsonp”) public void jsonp(@RequestParam(“callback”) String callback, @RequestParam(“hello”) String hello, HttpServletResponse response) throwsIOException { System.out.println(“前端给的数据 hello: “ + hello); String data = “我是第三方服务工程项目给出的数据”; response.setContentType(“text/javascript;charset=UTF-8”); response.getWriter().write(callback + “(“ + data + “)”); }允诺须要带上callback字符串Stringcallback,在该允诺的最后返回两个String, 该String返回用户网站的回调函数名(回调函数名(服务工程项目端的数据))response.setContentType(“text/javascript;charset=UTF-8”); response.getWriter().write(callback + “(“ + data + “)”); // 这里返回了两个正在回调函数调用的代码, 并且传递了参数 data

对了假如你得application.yml下有Spring Security的依赖, 可以加进下代码

server: port: 8080 spring: security: user: name: zhazhapassword: “{noop}123456”

布吕马服务工程项目端返回给前端 <script> 标签的字符串将被转化为 callback 函数继续执行

我们还可以采用 jquery 的方式调用

<!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <title>Title</title> <script src=“https://code.jquery.com/jquery-3.1.1.min.js”></script> </head> <body> <h1>布吕马案例</h1> <script> function callback(data) { console.log(data) alert(data) } $.ajax({url: “http://localhost:8080/jqJsonp”, type: “get”, dataType: “jsonp”, data: {hello: “hello”}, jsonp: “callback”, success: function (data) { alert(data) },error: function () { alert(“Wrong!”) } }); </script> </body> </html> @GetMapping(“jqJsonp”) @ResponseBody public String jqJsonp(@RequestParam(“callback”) String callback, @RequestParam(“hello”) String hello) { System.out.println(“前端给的数据 hello: “ + hello); String data = “我是第三方服务工程项目给出的数据”; return callback + “(“ + data + “)”; }

总结

jSONP的优点:

原理简单方便快捷, 随时可以搞

JSONP的缺点:

只支持GET允诺服务工程项目端须要修正代码发送的不是 XHR请求,无法采用XHR 对象(但这也是为甚么可以化解布吕马难题的根本)

这项技术在3-4年前可能还有人用, 现在基本上没甚么人用, 因为有更好的选择, 但思路较好

其他方法就不如是说了, 思路差不错, 比如说 PostMessage 等方案, 还有一部分借助iframe的, 可能被 Spring Security 拦截, 不好用

CORS布吕马天然资源共享(推荐)

CORS(Cross-Origin Resource Sharing)该技术由 W3C 为应用程序提供了一种布吕马天然资源共享方案

布吕马还有甚么思路?

可以告诉应用程序, 在两个搜索引擎之间开辟两个共享天然资源空间(或是管道), 这样两个搜索引擎就可以进行布吕马调用了, 这是一种思路

但这个共享天然资源空间肯定是有限制的

共享天然资源空间, 可以是 header 可以是 cookie, 又或是是 localStore 只要应用程序能够读取到的位置

那么CORS又是是不是同时实现的呢?

CORS是不是同时实现的?

CORS新增了一组HTTP允诺头字段,通过这些字段,服务工程项目器告诉应用程序,哪些网站通过应用程序有权限访问哪些天然资源。

简单允诺(可以不看)

假如站点 https://foo.example 的页面应用想要访问 https://bar.other 的天然资源。

foo.example 的页面中可能包含类似于下面的 JavaScript 代码:

const xhr = new XMLHttpRequest(); const url = https://bar.other/resources/public-data/; xhr.open(GET, url); xhr.onreadystatechange = someHandler; xhr.send();

此操作实行了客户端和服务工程项目器之间的简单交换,采用 CORS 首部字段来处理权限:

Spring Security怎么实现的跨域?

以下是应用程序发送给服务工程项目器的允诺报文:

GET /resources/public-data/HTTP/1.1Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflate Connection: keep-alive Origin: https://foo.example

允诺首部字段 Originhttp://foo.example

让我们来看看服务工程项目器怎样响应:

HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml […XML Data…]

本例中,服务工程项目端返回的 Access-Control-Allow-Origin标头的 Access-Control-Allow-Origin: * 值表明,该天然资源可以被任意外源访问。

Access-Control-Allow-Origin: *

采用OriginAccess-Control-Allow-Origin 就能完成最简单的访问控制。假如 https://bar.other 的天然资源持有者想限制他的天然资源只能通过 https://foo.example 来访问(也就是说,非 https://foo.example 域无法通过跨源访问访问到该天然资源),他可以这样做:

Access-Control-Allow-Origin: https://foo.example

备注:当响应的是附带身份凭证的允诺时,服务工程项目端明确 Access-Control-Allow-Origin 的值,而不能采用通配符“*”。

复杂允诺(可以不看)

了解有预检允诺便可

与简单允诺相同,“需预检的允诺”要求要首先采用OPTIONS方法发起两个预检允诺到服务工程项目器,以获知服务工程项目器与否允许该前述允诺。”预检允诺”的采用,可以避免布吕马允诺对服务工程项目器的用户数据产生未预期的影响。 如下是两个须要继续执行预检允诺的HTTP允诺:

const xhr = newXMLHttpRequest(); xhr.open(POST, https://bar.other/resources/post-here/); xhr.setRequestHeader(X-PINGOTHER, pingpong); xhr.setRequestHeader(Content-Type, application/xml); xhr.onreadystatechange = handler; xhr.send(<person><name>Arun</name></person>);

上面的代码采用POST允诺发送两个XML允诺体,该允诺包含了两个非标准的HTTP X-PINGOTHER允诺首部。这样的允诺首部并不是HTTP/1.1的一部分,但通常对于web应用很有用处。另外,该允诺的Content-Typeapplication/xml,且使用了自定义的允诺首部,所以该允诺须要首先发起“预检允诺”。

Spring Security怎么实现的跨域?

备注: 如下所述,前述的 POST 允诺不会携带 Access-Control-Request-* 首部,它们仅用于 OPTIONS 允诺。

下面是服务工程项目端和客户端完整的信息交互。首次交互是预检允诺/响应

OPTIONS /doc HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-aliveOrigin: https://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-TypeHTTP/1.1 204 No Content Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Keep-Alive: timeout=2, max=100 Connection: Keep-Alive

Copy to Clipboard

从上面的报文中,我们看到,第 1 – 10 行采用 OPTIONS方法发送了预检允诺,应用程序根据上面的JavaScript代码片断所采用的允诺参数来决定与否须要发送,这样服务工程项目器就可以回应与否可以接受用前述的允诺参数来发送允诺。OPTIONS 是 HTTP/1.1 协定中定义的方法,用于从服务工程项目器获取更多信息,是安全的方法。该方法不会对服务工程项目器天然资源产生影响。注意 OPTIONS 预检允诺中同时携带了下面两个首部字段:

Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Copy to Clipboard

首部字段 Access-Control-Request-Method 告知服务工程项目器,前述允诺将采用 POST 方法。首部字段 Access-Control-Request-Headers 告知服务工程项目器,前述允诺将携带两个自定义允诺首部字段:X-PINGOTHERContent-Type。服务工程项目器据此决定,该前述允诺与否被允许。

第 12 – 21 行为预检允诺的响应,表明服务工程项目器将接受后续的前述允诺方法(POST)和允诺头(X-PINGOTHER)。重点看第 15 – 18 行:

Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400

服务工程项目器的响应携带了 Access-Control-Allow-Origin: https://foo.example,从而限制允诺的源域。同时,携带的 Access-Control-Allow-Methods 表明服务工程项目器允许客户端采用 POSTGET 方法发起允诺(与 Allow 响应首部类似,但该标头具有严格的访问控制)。

首部字段 Access-Control-Allow-Headers 表明服务工程项目器允许允诺中携带字段 X-PINGOTHERContent-Type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。

最后,首部字段 Access-Control-Max-Age给定了该预检允诺可供缓存的时间长短,单位为秒,默认值是 5 秒。在有效时间内,应用程序无须为同一允诺再次发起预检允诺。以上例子中,该响应的有效时间为 86400 秒,也就是 24 小时。请注意,应用程序自身维护了两个最大有效时间,假如该首部字段的值超过了最大有效时间,将不会生效。

预检允诺完成之后,发送前述允诺:

POST /doc HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-alive X-PINGOTHER: pingpong Content-Type: text/xml; charset=UTF-8 Referer: https://foo.example/examples/preflightInvocation.html Content-Length: 55 Origin: https://foo.example Pragma: no-cache Cache-Control: no-cache <person><name>Arun</name></person> HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:40 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 235 Keep-Alive: timeout=2, max=99 Connection: Keep-Alive Content-Type: text/plain [Some XML payload]

实战

Spring的处理方式

注意, 假如你在工程项目中引入了 Spring Security, 那么下面几种 Spring 方式可能会报错, 也可能会失效

spring有三种方式处理布吕马问题

@CrossOrigin

// 允许 http://localhost:8080 网址的允诺 @CrossOrigin(origins = “http://localhost:8080”) @PostMapping(“post”) public String post() { return “hello, post”; }

@CrossOrigin注解各属性含义如下:

allowCredentials: 浏览器与否应当发送凭证信息,如Cookie。allowedHeaders: 允诺被允许的允诺头字段,表示所有字段。exposedHeaders: 哪些响应头可以作为响应的一部分暴露出来。注意,这里只可以一一列举,通配符在这里是无效的。maxAge: 预检允诺的有效期,有效期内不必再次发送预检允诺,默认是1800秒。methods: 允许的允诺方法,表示允许所有方法。origins: 允许的域,表示允许所有域。

源码分析

@CrossOrigin注解在AbstractHandlerMethodMapping 的内部类MappingRegistryregister方法中完成解析的,@CrossOrigin注解中的内容会被解析成两个配置对象CorsConfiguration将@CrossOrigin所标记的允诺方法对象HandlerMethodCorsConfiguration一一对应存入两个名为corsLookupMap集合中。当允诺到达 DispatcherServlet#fdoDispatch 方法之后,调用AbstractHandlerMapping#getHandlerHandlerExecutionChain时,会从corsLookupCorsConfiguration对象。CorsConfiguration对象构建两个CorsInterceptor拦截器。CorsInterceptor拦截器中触发对DefaultCorsProcessor#processRequest 的调用,布吕马允诺的校验工作将在该方法中完成。

全局布吕马方式

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 GlobalCorsConfig implements WebMvcConfigurer { /** * 配置全局布吕马化解方案 * * @param registry */ @Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping(“/**”) .allowedMethods(“*”) .allowedOrigins(“*”) .allowedHeaders(“*”) .allowCredentials(false) .exposedHeaders(“”) .maxAge(3600); } }

通过注册CorsFilter的方式

@Bean public FilterRegistrationBean<CorsFilter> corsFilter() { UrlBasedCorsConfigurationSource source =newUrlBasedCorsConfigurationSource(); CorsConfiguration config =new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin(“*”); config.addAllowedHeader(“*”); config.addAllowedMethod(“*”); source.registerCorsConfiguration(“/**”, config); FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(newCorsFilter(source)); bean.setOrder(-1); // 这行后面会解释, 为甚么? return bean; }

Spring Security处理方案

假如我们配置了 Spring Security的话, 上面的 Spring 化解布吕马难题的方式可能失效, 有的还可以采用, 为甚么呢?

通过@CrossOrigin注解或是重写addCorsMappings方法配置跨域,统统失效了, 通过CorsFilter 配置的布吕马,有没失效则要看冷却系统的优先级,假如冷却系统优先级高于Spring Security冷却系统,即先于Spring Security冷却系统继续执行,则CorsFilter 所配置的布吕马处理依然有效;假如冷却系统优先级低于Spring Security冷却系统,则CorsFilter 所配置的布吕马处理就会失效。这就是为甚么我们配置优先级为 -1

意思说, Spring Security你可以看作两个拦截器, 假如 Spring Security优先级高于我们自己配置的cors布吕马化解方案(在CorsIntercepter中校验), 那么就会以某些理由拦截下来, 而使得我们配置的布吕马失效, 那是为甚么呢?

我们知道, 在复杂允诺的情形下, 会发送两个预检允诺, 但他没携带任何的认证信息, 直接就会被 Spring Security拦截, 而等到复杂允诺发送过来之后, 该允诺没预检允诺的信息, 所以也是导致布吕马允诺失效

假如采用了CorsFilter配置布吕马,只要冷却系统的优先级高于Spring Security冷却系统,即在Spring Security冷却系统之前继续执行了布吕马允诺校验,那么就不会有难题。假如CorsFilter的优先级低于Spring Security冷却系统,则预检允诺一样须要先经过Spring Security冷却系统,由于没携带认证信息,在经过Spring Security冷却系统时就会被拦截下来。

那么是不是化解呢?

可以放行 OPTIONS 允诺, 但不安全可以继续采用前面的 通过注册CorsFilter的方式这种方式, 只要优先级比Spring Security高就行, 不过那也太不专业了

Spring Security的化解方案

importorg.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity; importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.web.cors.CorsConfiguration;importorg.springframework.web.cors.UrlBasedCorsConfigurationSource;@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl(“/”) .permitAll() .and() .cors().configurationSource(configurationSource()) .and().csrf().disable(); }privateUrlBasedCorsConfigurationSourceconfigurationSource() { final UrlBasedCorsConfigurationSource source = newUrlBasedCorsConfigurationSource();final CorsConfiguration config = newCorsConfiguration(); config.setAllowCredentials(true); // 允许cookies布吕马 config.addAllowedOrigin(“*”);// #允许向该服务工程项目器提交允诺的URI,*表示全部允许,在SpringMVC中,假如设成*,会自动转成当前允诺头中的Origin config.addAllowedHeader(“*”);// #允许访问的头信息,*表示全部 config.setMaxAge(18000L);// 预检允诺的缓存时间(秒),即在这个时间段里,对于相同的布吕马允诺不会再预检了 config.addAllowedMethod(“*”); source.registerCorsConfiguration(“/**”, config); return source; } }

源码就不分析了, 说白了就是借助上面的方法帮忙 new CorsFilter 冷却系统和填充内容

踩坑

我这里呢,给出一些建议,假如你的工程项目采用的spring gateway,也就是交换机的存在,那么你最好布吕马就不要在spring security中采用的。直接在spring gateway里面用就可以了。这样会少走很多坑。

因为spring security说白了是冷却系统,而spring gateway也是冷却系统,这两个冷却系统有的时候会串台。

特别是在发现极度的时候。有的时候会绕开双方的拦截器过程直接跳转到极度处理环节,这样就会再次出现难题。

这里一定要着重关注这一点。他们之间的次序很重要,再次出现极度也要防护。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务