springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】

文章目录

  • 1 开发流程简介
  • 2 UserConnection表介绍
  • 3 配置UsersConnectionRepository对象
  • 4 通过userId去我们的用户信息表里去拿用户信息
  • 5 将SocialAuthenticationFilter加入到spring-security的过滤器链中
    • 5.1 将SocialAuthenticationFilter的配置类注入到spring容器
    • 5.2 将SocialAuthenticationFilter通过apply()加入到spring-security过滤器链中
  • 6 增加一个QQ登陆按钮
    • 6.1 QQ登陆url中/auth的含义
    • 6.1 QQ登陆url中/qq的含义
    • 6.3 综合来看/auth/qq
  • 7 测试

项目源码地址 https://github.com/nieandsun/security

1 开发流程简介

上篇文章,我们开发完了从提供商(QQ)获取用户信息的代码。
获取到的用户信息会被springsocial封装成了一个Connection对象。而我们的目的是让spring-security/spring-social拿着该对象与我们系统内的对象进行比对校验,如果该对象可以和我们数据库里的某个对象信息完全匹配,则生成一个校验成功的对象Authentication,并将其放入SecurityContext,这也就完成了第三方登陆的整个流程。

这篇文章将在上几篇文章的基础上继续介绍利用QQ进行第三方登陆的整个流程开发。

2 UserConnection表介绍

UserConnection表是springsocial为我们准备的一张用来维护我们的系统和QQ、微信、微博等第三方应用之间关系的表,建表语句可在如下包目录结构下找到:
springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】_第1张图片
注意: 建表时可能会遇到一些问题,我专门为此写过一篇博客:【MySQL报错处理】[Err] 1064 、[Err] 1055、JdbcUsersConnectionRepository.sql建表报错

UserConnection表的表结构如下,前三个字段是一个联合主键,通过这三个字段其实就可以将我们系统的用户和提供商(QQ,微信等)唯一进行关联起来。表中的其他字段也都比较好理解,这里不再过多阐述。
springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】_第2张图片

3 配置UsersConnectionRepository对象

前面几篇文章基本完成了如下流程的开发:

用户点击第三方登陆按钮(如QQ登陆按钮)—> 跳转到QQ授权页面 —> 用户通过扫码等方式进行授权 —> 利用springsocial走oauth2协议的整个流程(获取授权码,通过授权码换accesstoken)—> 拿着accesstoken从QQ拿到openid,并拿着accesstoken,openid等从QQ获取到用户信息 —> 将获取到的用户信息,accesstoken,openid等封装成Connection对象。

要想拿着Connection对象与我们系统内的用户对象进行比对校验,从而完成第三方登陆的整个流程的话,接下来要做的第一件事就是去UserConnection表里通过Connection对象查找有没有对应的userId,这个过程springsocial已经帮我们写完了,但是要求我们配置一个UsersConnectionRepository对象的具体实现类。我们使用的实现类为JdbcUsersConnectionRepository,其具体配置如下:

package com.nrsc.security.core.social;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.security.SpringSocialConfigurer;

import javax.sql.DataSource;

/** * @author : Sun Chuan * @date : 2019/8/7 20:57 * Description: * UsersConnectionRepository的实现类,用来拿着Connection对象查找UserConnection表中是否与之相对应的userId * userId就是我们系统中的唯一标识,这个应该由各个系统自己根据业务去指定,比如说你系统里的username是唯一的, * 则这个useId就可以是username */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;


    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {

        /** * 第二个参数的作用:根据条件查找该用哪个ConnectionFactory来构建Connection对象 * 第三个参数的作用: 对插入到userconnection表中的数据进行加密和解密 */
        JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        //设置userconnection表的前缀
        repository.setTablePrefix("nrsc_");
        return repository;
    }
}

4 通过userId去我们的用户信息表里去拿用户信息

上一步中通过Connection对象拿到了userId这还没算完,因为spring-security还需要像校验用户名+密码登陆时那样去看一下该账户是否过期,是否被锁定等。springsocial也像用户名+密码校验时通过username获取用户信息一样,提供了一个方法loadUserByUserId,我们只需要实现SocialUserDetailsService接口并将其注入到spring容器,springsocial就可以调用到该方法。

由于loadUserByUserId 方法的返回值为SocialUserDetails,其实就是UserDetails的子类,因此可以将该方法和之前用户名+密码校验时实现的loadUserByUsername放在一起,代码如下:

package com.nrsc.security.server.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUser;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;

@Component("NRSCDetailsService")
@Slf4j
public class NRSCDetailsService implements UserDetailsService , SocialUserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

// @Override
// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//
// //点击页面的登陆时,你实际需要做的事为
// //1.拿着用户名去数据库里查询其密码
// //2.将拿到的用户名和密码封装到User里进行返回
//
// log.info("用户名为:" + username);
// //假设下面的密码是根据用户名获得的
// String password = passwordEncoder.encode("123456");
// log.info("password:" + password);
// //return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
//// return new User(username, password, true,true,true,false,
//// AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
// return new NrscUser(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
// }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("表单登录用户名:" + username);
        return buildUser(username);
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
        log.info("设计登录用户Id:" + userId);
        return buildUser(userId);
    }

    private SocialUserDetails buildUser(String userId) {
        // 根据用户名查找用户信息
        //根据查找到的用户信息判断用户是否被冻结
        String password = passwordEncoder.encode("123456");
        log.info("数据库密码是:"+password);
        return new SocialUser(userId, password,
                true, true, true, true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

5 将SocialAuthenticationFilter加入到spring-security的过滤器链中

springsoical为我们弄好了SocialAuthenticationFilter的配置类,我们只需要将其注入到spring容器,然后通过apply()的方式加入到spring-security的过滤器链中就可以了。

5.1 将SocialAuthenticationFilter的配置类注入到spring容器

我将该配置类注入spring容器的代码写在了本文第3部分的SocialConfig配置类中,代码如下:

    /** * 通过apply()该实例,可以将SocialAuthenticationFilter加入到spring-security的过滤器链 */
    @Bean
    public SpringSocialConfigurer nrscSocialSecurityConfig() {
        // 默认配置类,进行组件的组装
        // 包括了过滤器SocialAuthenticationFilter 添加到security过滤链中
        SpringSocialConfigurer springSocialConfigurer = new SpringSocialConfigurer();
        return springSocialConfigurer;
    }

5.2 将SocialAuthenticationFilter通过apply()加入到spring-security过滤器链中

代码这里不贴了,可以直接通过本篇提交代码的commit记录来查看:
项目源码地址https://github.com/nieandsun/security

6 增加一个QQ登陆按钮

代码如下:

<h3>社交登录h3>
<a href="/auth/qq">QQ登录a>

6.1 QQ登陆url中/auth的含义

SocialAuthenticationFilter会拦截所有路径中含有/auth的请求
springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】_第3张图片

6.1 QQ登陆url中/qq的含义

/qq其实是在构建ConnectionFactory对象时第一个参数对应的值。
springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】_第4张图片

6.3 综合来看/auth/qq

即当一个请求的url中有/auth/qq时,spring-security会让SocialAuthenticationFilter过滤器将其拦截下来,然后利用我们写的QQConnectionFactory对象来走oauth2流程+构建connection对象。

7 测试

点击QQ登陆会跳转到QQ的授权页面,但我们跳转到的页面里显示redirect uri is illegal(100010),具体原因将在接下来的博客中进行解决。
springsocial/oauth2---第三方登陆之QQ登陆3【完成整个开发流程】_第5张图片

你可能感兴趣的:(spring-security,spring-security)