Spring Security Note-9
开发QQ登录
所有的Api都继承AbstractOAuth2ApiBinding
;
在AbstractOAuth2ApiBinding
抽象类中有两个属性
1 | private final String accessToken; |
现在写的整个Api在整个流程当中,是负责执行第六步(获取用户信息),要执行第六步,需要第五步最后收到的令牌(Token),拿着令牌才能获取用户信息;
accessToken
:存取前五步完成后,获取到的令牌信息,这是一个类级别的全局对象,在整个我们需要完成的QQImpl中,不是一个单例对象,对每个用户都会有个QQImpl的单独的实现,然后存取用户自己独有的accessToken,这是一个多实例的对象;
restTemplate
:在第六步获取用户信息,要往服务器提供商发往一个HTTP请求,restTemplate就是帮助发送HTTP请求的;
获取用户信息之前,我们需要前查看QQ开发的文档QQ互联;
获取用户信息接口的实现
继承,AbstractOAuth2ApiBinding中accessToken是存放前面5步获取的用户信息的;
每个用户的用户信息都不相同,因此QQImpl是个多实例的;
因此不能将此类申明成为Spring的一个组件,需要的时候new就可以了;
需要的参数:
appId:注册qq互联分配的appid
openId:qq用户的Id
accessToken:父类提供
1 | public class QQImpl extends AbstractOAuth2ApiBinding implements QQ { |
1 | public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> { |
创建ConnectionFactory
Service Provider上部已完成,接下来开发另一部分ApiAdapter;
ApiAdapter将服务提供商用户基本信息进行统一的适配作用;
1 | public class QQAdapter implements ApiAdapter<QQ>{ |
调回自建Connection
1 | /** |
创建SocialConfig配置类
配置UserConnection表相关设置,注入SpringSocialConfigure;
1 | create table UserConnection (userId varchar(255) not null, |
1 |
|
添加配置
1 | public class QQProperties extends SocialProperties{ |
1 | public class SocialProperties { |
1 | "security") (prefix = |
1 |
|
1 | security.social.qq.app-id=xxx |
完善
在上部分完成所有组件的配置之后,测试QQ登录,会发现报错:
redirect uri is illegal(100010)
在我们进行QQ登录的时候引导用户到认证服务器并授权后跳转回引导的地址;
所以要求我们发起QQ登录的请求和我们在QQ互联系统上配置的请求QQ要保持一致;
否则跳转回来的时候,携带授权码的请求就不会被第三方应用处理,这就导致了发生错误redirect uri is illegal:
方法就是设置server.port=80,这样也能访问成功,引导用户到认证服务器也能成功;
1 | server.port=80 |
要想社交登录成功,我们最后还需要将spring social的过滤器配到我们的过滤器链中;
这个过滤器配置的Bean我们写在SocialConfig类中的ImoocSocialSecurityConfig方法中;
这个方法最终返回一个ImoocSpringSocialConfigurer对象;
1 | public class ImoocSpringSocialConfigurer extends SpringSocialConfigurer{ |
这个继承的父类SpringSocialConfigurer类中进行了过滤器的配置,在SpringSocialConfigurer类的源码中发现,在configure方法中先实例化了一个过滤器,然后将SocialAuthenticationFilter 过滤器加到了过滤器链中
1 | public class SocialProperties { |
1 | // 将SpringSocialFilter添加到安全配置的Bean |
第一步请求授权的过滤器SocialAuthenticationFilter类开始;
这个方法用户处理请求;
1 | auth = attemptAuthService(authService, request, response); |
对应流程图中SocialAuthenticationService类如何创建出Authentication认证信息;
传入的SocialAuthenticationService对象通过调用getAuthToken方法,直接就获取了通过授权码来获取到的token,由于SocialAuthenticationService是一个接口,具体的实现在SocialAuthenticationService接口的实现类OAuth2AuthenticationService的getAuthToken方法中;
实际上过滤器拦截的请求“\login\qq”既是第一步将用户导向认证服务器的请求,也是认证服务器返回授权码给第三方用户的返回请求,所以这个方法第一句就对这两种请求进行区分:
如果授权码code为空
则说明是第一次登陆,则抛出一个重定向的异常SocialAuthenticationRedirectException,参数
1 | getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params) |
发现这个地址就是我们之前在QQConnectionFactory中的QQServiceProvider参数中配置的地址拼凑起来的,通过这个地址将用户导向到QQ的用户登录页面;
如果授权码不为空
说明是QQ携带授权码返回给第三方应用的请求,
1 | AccessGrant accessGrant = getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, null); |
就是通过ConnectionFactory中的QQServiceProvider中的OAuth2Operations实现类来做拿着授权码去获取access_token的操作。OAuth2Operations接口的实现是我们之前配的OAuth2Template类;
在这个类中发起获取token的请求
1 | getRestTemplate().postForObject(accessTokenUrl, parameters, Map.class) |
1 | public class QQOAuth2Template extends OAuth2Template { |
通过获取token的请求accessTokenUrl来获取token等信息后封装在AccessGrant实例中,而且要求返回格式为map,为此我们复写的这个方法要求返回的格式为String.class,即返回字符串responseStr;
并截取出accessToken,expireIn ,refreshToken 这三个参数,最后保存到AccessGrant对象中;
此外复写的createRestTemplate方法也是为了RestTemplate 实例可以接受返回的UTF-8格式的字符串;
上部分时,成功封装了用户信息到了SocialAuthenticationProvider中出现了问题,页面重新引发跳转到signup;
在SocialAuthenticationProvider类中,有一个authenticate的方法,它接收一个Authentication;
接收它首先要判断进来的是一个SocialAuthenticationToken的信息;
拿到之后,会调用一个叫toUserId(connection)的方法;
在传入的connection当中,有服务提供商返回的用户信息,在key里面,有两个主要的属性:
providerId & providerUserId
SpringSocial拿到这两个值以后,在toUserId()方法里面,它会使用usersConnectionRepository去数据库中查询刚刚这个Key有没有对应的用户ID;
由于是第一次登录,肯定是不存在数据的,拿不到用户ID,将会抛出一个BadCredentialsException;
将会交由Social AuthenticationFilter处理,做出一个判断,判断这个过滤器中,singupUrl是否为空,否则会认为有一个注册的页面,在此我们应该去定义一个注册的页面;
1 |
|
1 |
|
为了使跳转到注册页面时携带QQ的相关信息和注册完成后,将第三方的唯一标示传递给Social插入userConnections数据库中;
1 | /** |
在BrowserSecurityController中添加获取QQ用户信息
1 |
|
封装SocialUserInfo
1 | public class SocialUserInfo { |
在用户进行第三方用户和QQ用户进行注册绑定时将用户唯一标示给Social,并插入UsersConnection;
1 |
|
还有一种情况,就是将QQ的用户信息默认注册为一个在第三方中的用户:
1 |
|
修改SocialConfig
1 | 1) ( |