问题描述:后端框架为SpringBoot,安全框架为Shiro。其中认证和授权都已经做好了,授权和认证主要是用注解的方式,主要采用了@RequiresAuthentication这个注解,意思是加上这个注解的方法,必须经过授权才可以访问,也就是必须登陆才可以。之后整个项目只用了Postman进行测试。
主要测试就是两方面,在不登陆的情况下去访问带有@RequiresAuthentication的方法,会返回没有权限。在登陆之后,就可以访问得到相应的资源。在postman测试完了之后,就以为Shiro配置完毕了。
后来,项目在线上进行测试的时候就发现问题了,也就是登陆之后(登录是成功的),在访问带有@RequiresAuthentication仍然报没有权限。。后来也进行了一些断点测试,就发现了SecurityUtils.getSubject().getPrincipal()在登陆之后是存在的,但是如果是另外一个请求这个值就是空的。。。
本来我在自己的印象里,shiro的操作是线程相关的,也就是可以通过线程来区分是哪个用户在操作。但是线程是多大的呢。。是每一个Http请求都新建一个线程吗?显然我觉得这是不可能的,但是我又找不到证据,所以就开始找资料,后来找到了这篇。https://blog.csdn.net/quanaianzj/article/details/83858575,这一片很详细的解释了Shiro的工作原理。直接看最终的结论:
也就是说,每次的subject内的用户信息,是从session中读取的,所以既然跟session有关系。前端发送的请求就必须带上jsessionid,这个是session的唯一凭证,一般这些事情都是浏览器帮你做好的。但是自己翻了翻浏览器的cookie发现自己并没有找到相关的cookie,但是在postman中找到了,如下图
所以走到这我就更加坚信了,是因为某种原因导致前端请求发送的时候没有带上cookie,或者说是登陆之后,后端根本就没有返回相应的cookie。。因为伟哥在他浏览器上测试也是没有cookie,所以到这我就知道应该是我后端的原因,而不是浏览器的问题。。后来终于发现罪魁祸首是跨域问题。参考如下http://www.cnblogs.com/nuccch/p/7875189.html。如下图
也就是说,当请求是跨域的,并且是用CORS协议解决的话,http请求或者ajax请求是不会带上服务器返回的cookie的,也就是说每一个ajax请求在后端看来都是一个全新的请求,都会创建一个新的session,而Shiro的用户认证信息都是放在session中的,所以信息自然也就找不到了,相应的认证也就会失败。。所以到此,解决问题的方案就是想办法让ajax请求的时候带上cookie。
经过查阅资料,需要在ajax请求中加入一个字段,如下图
加上一个xhrFields字段,意思是带上cookie一起发送。同样还需要在后端的解决跨域过滤器上加上一条语句,如下图
加上一个setAllowCredentials(true)。到此,本以为问题会解决,但是再次发送请求的时候仍然会出现错误。如下图
大意就是如果带上了cookie的请求,你后端的allowOrigin就不能设置成"*"了,所以就必须明确指定请求来源的哪个域。这就比较简单了,一般来说origin就是域名,本地请求话就是http://localhost: + 端口号。所以修改后端代码如下
至此,问题全部解决!当然了,上述直接把域名硬编码到代码中也是不优雅的,可以origin写入application.yml中去读取,域名修改的时候只需要修改配置文件就可以了。