记录一次微服务中使用SpringSecurity集成企业微信扫码登录-校验请求来源错误问题的解决
最近在项目中遇到一个平时不太注意的问题,记录一下方面后期继续深入跟踪,再近期项目中需要使用企业微信扫码登录,在将企业微信二维码嵌入到访问页面中的时候,结果二维码加载失败(校验请求来源错误),结果如下图所示。
场景复现
接入企业微信二维码方式
- 企业微信管理端添加访问白名单;
- 企业微信管理端设置可信域名(域名要保持和企业微信回调的域名相同)
- 必须要通过页面跳转打开访问生成企业微信二维码的链接
实现代码
登录页面(login.html)和跳转链接(忽略掉html布局略丑,单纯为了记录没有做美化)
<a href="https://testapi.xxxx.com/java/troy">企业微信</a>
<p></p>
<a href="https://testapi.xxxx.com/java/troy1">企业微信1</a>
<p></p>
nginx配置
location /java/login {
proxy_pass http://192.168.201.118:9999/java/login;
}
location /java/troy {
proxy_pass http://192.168.201.118:9999/java/troy;
}
location /java/troy1 {
proxy_pass http://192.168.201.118:9999/java/troy1;
}
springboot中springsecurity配置(这里省略掉其他配置)
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().disable()
.headers().hsts().disable().frameOptions().mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
.contentSecurityPolicy("default-src https: data: 'unsafe-inline' 'unsafe-eval'")
.and()//默认ReferrerPolicy是ReferrerPolicy.NO_REFERRER策略
.and()
.cors().configurationSource(corsFilter())
controller
@Controller
public class LoginController {
@GetMapping("/java/login")
public String loginPage(Model model) {
return "login";
}
}
@Slf4j
@Controller
public class TestController {
@GetMapping("/java/troy")
public String sso3(ServerWebExchange exchange) {
log.info("come in /troy");
String path = "redirect:https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=xxxxx&redirect_uri=https%3A%2F%2Ftestapi.xxxxx.com%2Fwxwork%2Fsso3-callback&state=073524&usertype=member";
log.info("end /troy");
return path;
}
}
操作流程:
- 首先浏览器打开登录地址LoginController("/java/login")
- 进入登录页面login.html
- 点击页面链接进入TestController("/java/troy")进行跳转到企业微信API,然后企业微信内部调起生成二维码,页面呈现二维码用户扫码
抛析问题
在使用springsecurity方式失败后,项目组有同学通过go来调用,采用同样的操作流程缺直接打开了企业微信二维码扫码页面,期间也和同事在网上搜了一些二维码加载不出来的问题,但是可用的解决方案没有找到,提到的一些点又太粗,后面通过对比java和go的跳转链接发现,通过跳转后的链接,失败的时候没有http referer属性。既然发现不同点了,那么就好办了看一下springsecurity框架对于referer的处理,再看看能不能加的上,想到这里直接开动起来。
跳转请求对比
我们通过登录页面,执行操作流程中的第3步时,可以通过跳转链接抓到请求查看区别:
#失败请求
https://aegis.qq.com/collect/pv?id=XfN&uin=&version=1.34.46&aid=xxxxx&platform=4&netType=4&sessionId=session-1198&from=https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=xxxx&redirect_uri=https%3A%2F%2Ftestapi.xxxx.com%2Fwxwork%2Fsso3-callback&state=073524&usertype=member&referer=
#成功请求
https://aegis.qq.com/collect/pv?id=N&uin=&version=1.34.46&aid=xxxx&platform=4&netType=4&sessionId=session-10763&from=https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=xxxx&redirect_uri=https%3A%2F%2Ftestapi.xxxx.com%2Fwxwork%2Fsso3-callback&state=073524&usertype=member&referer=https%3A%2F%2Ftestapi.xxxx.com%2F
通过对比发现失败的请求中缺少referer参数,通过查找springsecurity文档和源码发现,springsecurity会在response header中添加http referer策略,springsecurity默认使用的http referrer策略是ReferrerPolicy.NO_REFERRER。http referrer的介绍可以查看链接。那么到这里问题就简单了修改springsecurity配置,我是用的配置http referrer策略ReferrerPolicy.ORIGIN,大家可以按照自己项目需要选择合理的设置,这里简单讲一下这两个策略的意思,详细解释可以查看链接。
ReferrerPolicy.NO_REFERRER:no-referrer 整个 Referer 首部会被移除。访问来源信息不随着请求一起发送。(这也就解释了为什么失败的时候没有referer的值);
ReferrerPolicy.ORIGIN:origin 不管什么时候只使用origin作为引用地址。例如 www.troyqu.com/page.html的页面只会将www.troyqu.com作为引用地址。
解决问题
修改springboot中springsecurity配置
添加referrerPolicy(ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.ORIGIN)
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().disable()
.headers().hsts().disable().frameOptions().mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
.contentSecurityPolicy("default-src https: data: 'unsafe-inline' 'unsafe-eval'")
.and().referrerPolicy(ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.ORIGIN).and()//默认ReferrerPolicy是ReferrerPolicy.NO_REFERRER策略
.and()
.cors().configurationSource(corsFilter())
重启服务后访问发现,企业微信二维码可以打开了,到这里所有问题都彻底解决了。
那么既然springsecurity有设置http referer那么我们也可以对比下看看是不是更改配置后,对应的参数也进行了更新呢?
默认配置的控制台http参数
可以看到response header中的referer策略已经更新,设置为no-referrer(再次验证)
内部LoginController跳转到TestController接口的时候,request header中无referer参数。
修改后的控制台http参数
可以看到response header中的referer策略已经更新,设置为origin
内部LoginController跳转到TestController接口的时候,request header中referer参数值也和origin一样,没有其他的http path信息。
总结
- springsecurity默认使用ReferrerPolicy.NO_REFERRER:no-referrer的策略,当我们需要访问一些其他厂商的API需要验证referrer的时候,会因为请求缺少referrer导致请求被验证不合法,从而导致请求处理失败。
- 之前对于http referrer参数没有进行太深入了解,后面需要补一下相关知识。