https://blog.csdn.net/q610376681/article/details/86546935
1.redirect url is illegal
1.1 域名问题
我们访问时候出现如下错误:
在之前的第三步,返回Client并携带授权码,返回的Client的地址就是就是现在的redirect地址,地址是如何确定的呢,就是我们在QQ互联上注册时要填写一个回调域,回调域只要配置一个域名就可以了.
我们现在的回调域是下图,我们现在的回调域是有问题的,回调域只要填写域名就可以了,比如365.com,我们传过去的uri是下图,我们访问的地址与服务器跳转回来的地址都应该是登陆时填写的地址.即/auth/qq,那我们现在的uri是localhost,实际回调的是365.com,我们现在要做的就是让redirect参数和rederect地址保持一致.
现在我们将365.com映射到了本机地址,但是它访问的是80端口,因为安全的原因,80端口被禁用了,所有访问80端口的都会跳到9090端口,所以在application.properties中做如下配置.
结果就是当访问www.pinzhi365.com,就会访问本机的localhost:9090端口.
1.2 地址问题.
现在我们的地址是/auth/qq,如何改变呢?
我们之前配过SpringSocialConfigure,并将它注了进去
我们之前在spring-security-core里面配置过过SpringSocialConfigure
在spring-security-web里面注入了:
我们查看SpringSocialConfigurer代码,里面有一个configure方法,此方法是new了一个SocialAuthenticationFilter,然后将其加入到过滤器前的AbstractPreAuthenticatedProcessingFilter前面,加入到之前执行了postProcess方法:
protected <T> T postProcess(T object) {
return this.objectPostProcessor.postProcess(object);
}
addFilterBefore((Filter)this.postProcess(filter), AbstractPreAuthenticatedProcessingFilter.class);
我们现在要做的是继承SpringSocialConfigurer,把方法:postProcess覆盖掉:
public class MySpringSocialConfigurer extends SpringSocialConfigurer {
private String filterProcessesUrl;
public MySpringSocialConfigurer(String filterProcessesUrl){
this.filterProcessesUrl = filterProcessesUrl;
}
@Override
protected <T> T postProcess(T object) {
/**
* 1.我们覆盖SpringSocialConfigurer的postProcess
* 2.里面的object就是我们之前:SocialAuthenticationFilter
*/
//1.获取父类处理的结果
SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);
//2.设置FilterProcessesUrl,由于我们每个应用传递的FilterProcessesUrl可能是不一样的,所以我们将其作为可配置的
filter.setFilterProcessesUrl(filterProcessesUrl);
return super.postProcess(object);
}
}
然后我们在SocialConfig里面配置成我们自己定义的类
SecurityProperties属性为:
public class SocialProperties {
/**
* SocialAuthenticationFilter的默认过滤器url是:/auth
*/
private String filterProcessesUrl="/auth";
private QQProperties qq = new QQProperties();
//setter getter
}
然后我们第三方应用配置文件去配置application.yml:
yxm:
security:
social:
filterProcessesUrl: /qqLogin
qq:
app-id: xxx
app-secret: xxxx
providerId: callback.do
然后我们去前端配置(现在我们更改了配置filterProcessUrl为申请应用的后缀:/qqLogin):
<h3>社交登录</h3>
<a href="/qqLogin/callback.do">QQ登录</a>
所以以上地址也是第三步骤中引导客户访问的跳转地址:/qqLogin/callback.do 和qq互联的配置地址是一致的。 用户授权完跳回来也是在这个地址上。
跳进去之后:
提示没有认证:看下请求的日志:
因为默认情况下,我们是没有配置:sigin拦截请求的
我们先看下spring social执行第三方登录时候的代码:主要接口、实现类、以及调用顺序图:
- 与验证码、短信拦截登录请求是一样的。
- 过滤器去拦截请求,将信息包装到Authentication实现中,然后传递给AuthenticationManager,Manager根据其管理的实现不同挑一个provider来处理传进来的校验信息,在处理的过程中其会调用我们书写的SocialUserDetailsService接口的实现来获取用户的信息,将用户的信息封装到SocialUserDetails接口的实现中,进行检查和校验,如果都通过了那么会把用户信息放到我们之前封装的Authentication中,然后把Authentication标记成经过认证的.
- 在SpringSocial提供的第三方登陆中涉及了一些特殊的东西,比如说过滤器在封装Authentication给Manager的时候,用到的一个接口叫做SocialAuthenticationService,此Service将执行整个OAuth的流程,在执行的过程中会调用我们的ConnectionFactory,ConnectionFactory会拿到ServiceProvider,ServiceProvider中有一个OAuth2Operations,它会帮助SpringSocial完成整个流程,完成流程后会拿到服务提供商的信息,服务提供商的信息会封装到Connection中,Connection会被封装到SocialAuthenticationToken,Token会被交由Manager中,然后被交由SocialAuthenticationProvider来处理此Token,provider在处理时会根据传过来的connetion里的服务提供商的信息,使用我们jdbcUsersConnectionRepository去从数据库中查询一个UserId出来,查找到UserId再调用我们的SocialUserDetialsService查出来我们的SocialUserDetails,最后再把SocialUserDetails放在SocialAuthenticationToken中,标记为已经过身份认证,放在SecurityContext里,最终放在session里.
蓝色的都是系统实现的,橘色的是我们自己写的
上面出现问题也是因为:在我们的授权蓝色模块出现问题,signin没有请求拦截到:也就是OAuth2AuthenticationService问题。我们追踪下代码:
判断有没有授权码,如果没有就抛出异常.如果有就拿授权码去换令牌.通过打断点我们发现是换令牌的过程中出现了异常.
public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
String code = request.getParameter("code");
//第3步和第4步骤都是会走此逻辑/qqLogin/callback.do;我们根据是否有授权码来判断是第3步还是第4步。
if (!StringUtils.hasText(code)) {//有授权码:说明是第四步:此时我们会去操作连接工厂
OAuth2Parameters params = new OAuth2Parameters();
params.setRedirectUri(this.buildReturnToUrl(request));
this.setScope(request, params);
params.add("state", this.generateState(this.connectionFactory, request));
this.addCustomParameters(params);
throw new SocialAuthenticationRedirectException(this.getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
} else if (StringUtils.hasText(code)) {//没有授权码:说明是第3步:此时我们会调用服务提供商的getOAuthOperations()的exchangeForAccess
try {
String returnToUrl = this.buildReturnToUrl(request);
AccessGrant accessGrant = this.getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, (MultiValueMap)null);
Connection<S> connection = this.getConnectionFactory().createConnection(accessGrant);
return new SocialAuthenticationToken(connection, (Map)null);
} catch (RestClientException var7) {
this.logger.debug("failed to exchange for access", var7);
return null;
}
} else {
return null;
}
}
this.getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, (MultiValueMap)null)
上面会通过OAuth2Template调用:
打开OAuthOperations实现类,打开postAccessGrant,发现它是拿自己的OAuthTemplate发了一个OAuth请求,请求的数据在回来的时候要转成一个map,用restTemplate表示期望返回的是json格式的数据,然而上面的信息显示返回的格式是text/html.
返回的错误显示着不能抽取QQ返回的信息(qq信息是对象,只有application/json类型才能转换成json对象,但是我们这里返回的是:text/html),可以看到它是拿OAuthOperations去交换的令牌.
所以抛出了异常,并返回了null,继续往下走,Token是空,继续返回空.如果它是空,抛出异常,打开失败处理器,发现会重定向到signIn页面上去,又因为SignIn页面没授权,最后出现了问题.
面对这种情况,我们替换掉默认的OAuth2Temeplate,写一个自己的实现,由于是在createRestTemplate出问题,所以我们重写此方法 然后在自己的实现里加一个MessageConverter就可以了.
新建类QQOAuth2Template: 默认情况下是有3个convter,但是都不满足我们要求,需要我们自己添加一个。
public class QQOAuth2Template extends OAuth2Template {
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
}
@Override
protected RestTemplate createRestTemplate() {
//1.先拿到父类创建结果:我们需要使用父类的部分功能,只是需要在此基础上添加而已
RestTemplate restTemplate = super.createRestTemplate();
//2.获取消息转换类
List<HttpMessageConverter<?>> messageConverters =
restTemplate.getMessageConverters();
//3.添加自定义的消息转换类。
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
解决了整个问题还有一个问题
按照OAuth模板的想法,发出去rest请求后,返回的结果应该是json,把json解析成map,传给extractAccessGrant方法.在这个方法中从map中去获取access_token,scope,expireIn等等
然后拿这四个字段去new一个AccessGrant,这个类是对OAuth中Tokend的一个封装.
但是我们这里返回的不是json,是一个字符串,所以我们不能解析成map,应该自己写解析工具
最后一个问题:
我们通过OAuth2Template调用exchangeForAccess时候,一共有5个参数:
与之相对的qq互联里面也有5个参数:
我们应该对应起来,但是我们userParameterForClientAuthentication默认是没有值false,我们要改成true才行.
把原来的provider中的Auth2Template改成QQOAuth2Template
一切准备就绪后,我们调试时候,又出现一个跳转:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。