2023-06-18
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81942345

处理注册逻辑

获取完用户信息,就跳转到了 http://mrcode.cn/signup ; 注册页面

为什么会跳转到注册页面呢?学习了这么长时间,核心基本原理也了解了,解决问题的方法也了解了,
那么先靠自己来尝试解决下,一步一步的跟着代码,发现报了一个错误

    org.springframework.social.security.SocialAuthenticationProvider#authenticate
    
    String userId = toUserId(connection);
      if (userId == null) {
        throw new BadCredentialsException("Unknown access token");
      }
    
      protected String toUserId(Connection<?> connection) {
            List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
            // only if a single userId is connected to this providerUserId
            return (userIds.size() == 1) ? userIds.iterator().next() : null;
        }
    看源码,这里使用了查询数据库,没有获取到userId,标识该用户还没有在我们的业务系统中绑定

设置默认跳转到注册页面

就是把默认的/signUp路径修改成我们自己的路径;

提供一个配置,支持使用方自定义注册页面

    @Bean
    public SpringSocialConfigurer imoocSocialSecurityConfig() {
        // 默认配置类,进行组件的组装
        // 包括了过滤器SocialAuthenticationFilter 添加到security过滤链中
        SpringSocialConfigurer springSocialConfigurer = new SpringSocialConfigurer();
        springSocialConfigurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
        return springSocialConfigurer;
    }

怎么让注册动作与social互动?

  1. 怎么拿到用户授权后获取的用户信息?
  2. 怎么让注册这个动作与social互动(也就是要把关联信息插入到数据库中)

关键工具:org.springframework.social.connect.web.ProviderSignInUtils
目前不知道这个工具从哪里来的;

原理:原理就是把存储在session中的用户信息获取到;

    org.springframework.social.security.SocialAuthenticationFilter#doAuthentication
    private Authentication doAuthentication(SocialAuthenticationService<?> authService, HttpServletRequest request, SocialAuthenticationToken token) {
      try {
        if (!authService.getConnectionCardinality().isAuthenticatePossible()) return null;
        token.setDetails(authenticationDetailsSource.buildDetails(request));
        Authentication success = getAuthenticationManager().authenticate(token);
        Assert.isInstanceOf(SocialUserDetails.class, success.getPrincipal(), "unexpected principle type");
        updateConnections(authService, token, success);         
        return success;
      } catch (BadCredentialsException e) {
        // connection unknown, register new user?
        if (signupUrl != null) {
          // 跳转到注册页面前把连接信息存入session
          //
          sessionStrategy.setAttribute(new ServletWebRequest(request), ProviderSignInAttempt.SESSION_ATTRIBUTE, new ProviderSignInAttempt(token.getConnection()));
          throw new SocialAuthenticationRedirectException(buildSignupUrl(request));
        }
        throw e;
      }
    }

实现:

  1. 提供获取用户信息的接口
  2. 提供注册方法,然后使用工具类交互插入数据库,再次登录的时候就不会再次跳往注册页面了

获取用户的接口:

    cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityController
    /**
     * see {@link SocialConfig#providerSignInUtils(org.springframework.social.connect.ConnectionFactoryLocator, org.springframework.social.connect.UsersConnectionRepository)}
     */
    @Autowired
    private ProviderSignInUtils providerSignInUtils;
    @GetMapping("/social/user")
    public SocialUserInfo getSocialUserInfo(javax.servlet.http.HttpServletRequest request) {
        SocialUserInfo userInfo = new SocialUserInfo();
        Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
        userInfo.setProviderId(connection.getKey().getProviderId());
        userInfo.setProviderUserId(connection.getKey().getProviderUserId());
        userInfo.setNickname(connection.getDisplayName());
        userInfo.setHeadimg(connection.getImageUrl());
        return userInfo;
    }

提供注册方法:在demo里面,因为注册的逻辑是使用方才知道

    com.example.demo.web.controller.UserController
    
    @PostMapping("/regist")
    public void regist(User user, HttpServletRequest request) {
    
        //不管是注册用户还是绑定用户,都会拿到一个用户唯一标识。
        String userId = user.getUsername();
        // 在这里就可以执行绑定或则注册用户的逻辑了
        // 然后使用 doPostSignUp 进行插入数据库
        providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));
    }

怎么让没有查询到userId的用户不跳转到注册页面,默认注册一个账户?

在这样的场景下;有一部分网站是使用qq登录就默认为你注册一个账户。然后直接完成登录;

原理是:

    之前的源码中没有获取到 userId的地方 :
    org.springframework.social.security.SocialAuthenticationProvider#toUserId
    protected String toUserId(Connection<?> connection) {
      List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
      // only if a single userId is connected to this providerUserId
      return (userIds.size() == 1) ? userIds.iterator().next() : null;
    }
    
    org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository#findUserIdsWithConnection
    
    public List<String> findUserIdsWithConnection(Connection<?> connection) {
            ConnectionKey key = connection.getKey();
            List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());      
            if (localUserIds.size() == 0 && connectionSignUp != null) {
          // 注意这里,connectionSignUp 可以返回一个新的userid
          // 在这里就可以插入我们自己的逻辑,比如默认注册用户
                String newUserId = connectionSignUp.execute(connection);
                if (newUserId != null)
                {
                    createConnectionRepository(newUserId).addConnection(connection);
                    return Arrays.asList(newUserId);
                }
            }
            return localUserIds;
        }

实现:由于这样个性化的也属性使用方控制

    package com.example.demo.security;
    
    import cn.mrcode.imooc.springsecurity.securitycore.social.SocialConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.social.connect.Connection;
    import org.springframework.social.connect.ConnectionSignUp;
    import org.springframework.stereotype.Component;
    
    /**
     * 第三方登录,默认注册用户
     * @author : zhuqiang
     * @version : V1.0
     * @date : 2018/8/6 20:04
     * @see SocialConfig#connectionSignUp  该对象存在则会在该地方被使用
     */
    @Component
    public class DemoConnectionSignUp implements ConnectionSignUp {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public String execute(Connection<?> connection) {
            logger.info("根据社交用户信息默认创建用户并返回用户唯一标识");
            return connection.getDisplayName();
        }
    }

构建UsersConnectionRepository的时候把ConnectionSignUp实现设置进去

    @Configuration
    @EnableSocial
    public class SocialConfig extends SocialConfigurerAdapter {
        @Autowired
        private SecurityProperties securityProperties;
        @Autowired
        private DataSource dataSource;
    
        /**
         * 不存在则不使用默认注册用户,而是跳转到注册页完成注册或则绑定
         */
        @Autowired(required = false)
        private ConnectionSignUp connectionSignUp;
    
        @Override
        public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
            JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
            repository.setTablePrefix("imooc_");
            repository.setConnectionSignUp(connectionSignUp);
            return repository;
        }

运行查看效果:直接默认插入了一条数据到数据库中完成了默认的注册;直接认证登录成功

    INSERT INTO `imooc-demo`.`imooc_userconnection` (`userId`, `providerId`, `providerUserId`, `rank`, `displayName`, `profileUrl`, `imageUrl`, `accessToken`, `secret`, `refreshToken`, `expireTime`) VALUES ('猪', 'qq', '81F03E50B76D6D829F5A4875941567A6', '1', '猪', NULL, 'http://thirdqq.qlogo.cn/qqapp/101316278/81F03E50B76D6D829F5A4875941567A6/40', '2FCF43C2BA45ECD4CA4508FC8DC2CED8', NULL, 'D5C83B6F2E95DA9B4B97849113F15972', '1541333450678');
阅读全文