为何跳转signup
在上一章结尾中,扫码登录后就会调到就跳到signup注册页面,这是怎么回事呢?我们来跟下源码。
org.springframework.social.security.SocialAuthenticationProvider#authenticate
org.springframework.social.security.SocialAuthenticationFilter#doAuthentication
默认注册界面
分析可知:用户首次进行社交登录,社交用户和系统用户还没有进行绑定,所有会调到social默认的注册界面signup ,但是这个路径我们在浏览器权限配置中没有放行,所以才会又跳到我们的认证地址。
修改默认注册界面
就是把默认的/signup路径修改成我们自己的路径,这个注册界面通常应该是在我们的demo项目中处理
主要添加这一行
在权限配置类中要放行这个路径:
在配置文件类中添加注册路径
配置文件中添加demo-signup.html
demo注册界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo注册界面</title>
</head>
<body>
<h2>demo注册界面</h2>
<h3>表单注册</h3>
<form action="/user/regist" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2">
<button type="submit" value="bind">绑定</button>
<button type="submit" value="regist">注册</button>
</td>
</tr>
</table>
</form>
</body>
</html>
ok 我们来测试下 看是否可以跳到我们的自定义界面
注册:社交用户首次登陆,系统中还没有用户信息。
绑定:将社交信息和系统中已有的用户信息进行绑定。
social与注册互动
1.通常在注册或绑定逻辑执行之前(/uesr/regist),注册界面会显示一些社交用户信息如昵称图像并提示用户进行注册动作,怎么拿到用户信息呢。
2.还有怎么将系统用户信息和social互动?也就是要把关联信息插入到表UserConnection中
关键工具:org.springframework.social.connect.web.ProviderSignInUtils
配置 ProviderSignInUtils
com.rui.tiger.auth.core.social.SocialConfig#providerSignInUtils
/** * social和注册互动工具类 * @return */ @Bean public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator){ return new ProviderSignInUtils(connectionFactoryLocator,getUsersConnectionRepository(connectionFactoryLocator)); }
SocialUserInfo 定义社交用户信息,可以给前台注册界面进行展示
package com.rui.tiger.auth.browser.support;
import lombok.Data;
/**
* 社交用户信息封装
* @author CaiRui
* @date 2019-01-09 18:24
*/
@Data
public class SocialUserInfo {
private String providerId;//供应商ID
private String providerUserId;//供应商用户ID 即openID
private String nickName;//昵称
private String headImg;//图像地址
}
BrowserRequireController 浏览器控制类新增获取社交用户信息
package com.rui.tiger.auth.browser.controller;
import com.rui.tiger.auth.browser.support.SocialUserInfo;
import com.rui.tiger.auth.core.properties.SecurityProperties;
import com.rui.tiger.auth.core.support.SimpleResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 用户登录认证控制器
*
* @author CaiRui
* @date 2018-12-5 12:44
*/
@RestController
@Slf4j
public class BrowserRequireController {
//封装了引发跳转请求的工具类 https://blog.csdn.net/honghailiang888/article/details/53671108
private RequestCache requestCache = new HttpSessionRequestCache();
// spring的工具类:封装了所有跳转行为策略类
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
private static final String HTML_SUFFIX = ".html";
@Autowired
private ProviderSignInUtils providerSignInUtils;
/**
* 当需要进行身份认证的时候跳转到此方法
*
* @param request 请求
* @param response 响应
* @return 将信息以JSON形式返回给前端
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("BrowserRequireController进来了 啦啦啦");
// 从session缓存中获取引发跳转的请求
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (null != savedRequest) {
String redirectUrl = savedRequest.getRedirectUrl();
log.info("引发跳转的请求是:{}", redirectUrl);
if (StringUtils.endsWithIgnoreCase(redirectUrl, HTML_SUFFIX)) {
// 如果是HTML请求,那么就直接跳转到HTML,不再执行后面的代码
redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页面");
}
/**
* 获取社交用户信息 用于注册界面显示用户信息
*
* @param request
* @return
*/
@GetMapping("/social/user")
public SocialUserInfo getSocialUserInfo(HttpServletRequest request) {
SocialUserInfo socialUserInfo = new SocialUserInfo();
//通过工具类获取社交用户信息
Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
socialUserInfo.setProviderId(connection.getKey().getProviderId());
socialUserInfo.setProviderUserId(connection.getKey().getProviderUserId());
socialUserInfo.setNickName(connection.getDisplayName());
socialUserInfo.setHeadImg(connection.getImageUrl());
return socialUserInfo;
}
}
下面我们来让注册和social互动,注册这个逻辑应该是在第三方系统中实现的,所以我们放在demo项目中
package com.rui.tiger.auth.demo.controller;
import com.rui.tiger.auth.demo.vo.UserVo;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 用户控制器
*
* @author CaiRui
* @date 2018-12-6 8:16
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ProviderSignInUtils providerSignInUtils;
@RequestMapping("/hello")
public String hello() {
return "Hello,World";
}
/**
* 获取用户认证信息
*
* @return
*/
@GetMapping("authentication")
public Authentication getCurrentAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 获取用户认证信息
* 同getCurrentAuthentication spring 会帮我们注入
*
* @param authentication
* @return
*/
@GetMapping("authentication/auto")
public Authentication getCurrentAuthentication2(Authentication authentication) {
return authentication;
}
/**
* 社交注册
*/
@PostMapping("/regist")
public void regist(UserVo user, HttpServletRequest request){
// 不管是注册用户还是绑定用户,都会拿到一个用户唯一标识
String username=user.getUsername();
//这里处理绑定或注册用户逻辑
//进行系统用户和社交用户入库动作
providerSignInUtils.doPostSignUp(username,new ServletWebRequest(request));
}
}
别忘了对/user/regist 进行放行 com.rui.tiger.auth.browser.config.BrowserSecurityConfig#configure
ok 下面我们来测试下注册逻辑,扫码登录后跳到我们的注册界面
点击绑定或注册看看,发送 post请求: /user/regist
同时我们的数据库中也有这条记录绑定了
这里要注意的是这里只是绑定了 ,并没有进入认证。可以访问/user/hello 试试
要返回登录界面再次点击qq社交登录,根据前面的源码分析 由于我们的库中已经有记录的,不会跳到注册界面,同时会完成认证。
ok 注册成功完成。
直接绑定社交账户
如何做到社交账户首次登录,不用注册,我们后台直接给注册呢? 还是来分析源码,看看有没有解决办法。
org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository#findUserIdsWithConnection
所以我们要 实现这个接口 ConnectionSignUp ,放在我们的第三方系统中
package com.rui.tiger.auth.demo.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Component;
/**
* 第三方应用登录 默认注册用户
* @author CaiRui
* @date 2019-01-10 18:20
*/
@Component
@Slf4j
public class DemoConnectionSignUp implements ConnectionSignUp {
/**
* 根据社交用户信息默认创建用户并返回用户唯一标识
* @param connection
* @return
*/
@Override
public String execute(Connection<?> connection) {
log.info("根据社交用户信息默认创建用户并返回用户唯一标识");
//系统业务逻辑处理 目前直接以昵称作为唯一标识 这里 可以调用注册逻辑
return connection.getDisplayName();
}
}
com.rui.tiger.auth.core.social.SocialConfig#getUsersConnectionRepository 也同步调整
/**
* 业务系统用户和服务提供商用户对应关系,保存在表UserConnection
* JdbcUsersConnectionRepository.sql 中有建表语句
* userId 业务系统Id
* providerId 服务提供商的Id
* providerUserId 同openId
* Encryptors 加密策略 这里不加密
*
* @param connectionFactoryLocator
* @return
*/
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
//设定表UserConnection的前缀 表名不可以改变
//jdbcUsersConnectionRepository.setTablePrefix("tiger_");
if(connectionSignUp!=null){
jdbcUsersConnectionRepository.setConnectionSignUp(connectionSignUp);
}
return jdbcUsersConnectionRepository;
}
ok 测试之前先删除前面注册已经入库的记录
怎么设置登录成功后的默认界面呢 defaultSuccessUrl("/index.html") 就是这么简单 跟shiro差不多
package com.rui.tiger.auth.core.config;
import com.rui.tiger.auth.core.properties.SecurityConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* 密码登录的通用安全配置
* @author CaiRui
* @date 2018-12-26 18:11
*/
public class AbstractChannelSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler tigerAuthenticationFailureHandler;
/**
* 密码登录配置
* @param http
* @throws Exception
*/
protected void applyPasswordAuthenticationConfig(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)//
.defaultSuccessUrl("/index.html")
.successHandler(tigerAuthenticationSuccessHandler)
.failureHandler(tigerAuthenticationFailureHandler);
}
}
ok 到此qq登录完成 ,下一章我们将开发微信社交登录。