处理注册逻辑
获取完用户信息,就跳转到了 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互动?
- 怎么拿到用户授权后获取的用户信息?
- 怎么让注册这个动作与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;
}
}
实现:
- 提供获取用户信息的接口
- 提供注册方法,然后使用工具类交互插入数据库,再次登录的时候就不会再次跳往注册页面了
获取用户的接口:
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');
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。