一、理论基础
1.1、为什么需要做单点登陆系统
大型互联网公司中,项目旗下可能会有多个子系统,每个登录实现统一管理,多个账户实现统一管理。
例子:比如阿里旗下支付宝,咸鱼等,只要有一个系统登录情况下,其他就不需要登录了(或者直接拿一个授权就行了)
(XXL-SSO是国产的,CAS相对来说好点)
1.2、单点登陆系统实现思路
核心:靠认证授权中心
1.3、单点登陆系统框架
xxl-sso :https://github.com/xuxueli/xxl-sso
xxl-sso官方文档:http://www.xuxueli.com/xxl-sso/#/
需要修改XXL-SSO框架的:
- 修改XXL-SSO认证服务授权页面样式
- 修改XXL-SSO认证服务数据库账号密码
http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/
http://xxlssoclient2.com:8085/xxl-sso-web-sample-springboot/
客户端访问http://xxlssoclient1.com:8084重定向http://xxlssoserver.com:8080(SSO认证授权系统)
登陆成功之后跳转原来地址:
http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_1db53a90152140c6a8421c83546f75ab
疑问1:为什么在认证授权中心登陆成功之后,会带一个xxl_sso_sessionid参数?
疑问2:为什么ssoClient需要集成redis?
疑问3:认证授权中心如何实现集群化?
疑问4:访问客户端的时候,如何自动重定向到认证授权中心的?
1.4、Client原理分析
原理:
- 先从Cookie中获取当前CookieId
- 如果用户没有登陆的情况下,重定向到认证授权中心
- 认证授权登陆成功之后,在认证授权域名下存放对应的cookie信息
- 认证授权系统回调到子系统中传递xxl_sso_sessionid
- 回调到子系统的时候,进XxlSsoWebFilter拦截 获取xxl_sso_sessionid 信息
- 子系统使用xxl_sso_sessionid从redis查询认证授权系统登陆的用户信息,将用户信息在子系统域名下存放对应的用户Cookie信息
http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/
疑问1:为什么redirect_url地址在认证授权中心登陆成功之后,返回原来地址 (回调地址)?
疑问2:其他其他子系统如何实现面密登陆的呢?
1.5、XXL-SSO认证授权中心如何实现高可用
使用Nginx实现xxl-sso-server认集群和故障转移
upstream backServer{
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name xxlssoserver.com;
location / {
proxy_pass http://backServer;
###nginx与上游服务器(真实访问的服务器)超时时间 后端服务器连接的超时时间_发起握手等候响应超时时间
proxy_connect_timeout 1s;
###nginx发送给上游服务器(真实访问的服务器)超时时间
proxy_send_timeout 1s;
### nginx接受上游服务器(真实访问的服务器)超时时间
proxy_read_timeout 1s;
index index.html index.htm;
}
}
1.6、XXL-SSOToken认证
注意:基于token令牌模式的单点登录只适合移动端(客户端)不适合web系统(java)
注意:vue或者react(客户端技术)是通过token令牌模式实现单点登录
如何保证SSO-Server高可用的问题
可以考虑SSO-Servet集群问题
思考:SSOClient实现注销,URL地址如何拼接
SSO基于Cookie 形式 、Token形式
SSO基于Token形式的实现 普通登陆一模一样的路程
基于令牌形式实现SSO
1. 调用认证授权中心接口登陆返回用户令牌信息
2. SSOClient项目需要实现单点登陆的话,在调用SSOClient接口的时候请求头中传递Token信息
3. sessionId 相当于Token
二、代码集成XXL-SSO开源项目
A. 认证授权中心xxl-sso-server
修改登陆数据库验证
修改页面静态资源
B. SSOClient端整合
(1)创建认证授权中心shop-basics-xxl-sso-server
Maven依赖信息:
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- sso core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
</dependency>
<!-- springcloud feign组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.mayikt</groupId>
<artifactId>meite-shop-service-api-member</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mayikt</groupId>
<artifactId>meite-shop-common-web-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
(2)改造登陆代码
userLoginInpDTO.setMobile(username);
userLoginInpDTO.setPassword(password);
userLoginInpDTO.setLoginType(Constants.MEMBER_LOGIN_TYPE_PC);
String info = webBrowserInfo(request);
userLoginInpDTO.setDeviceInfor(info);
BaseResponse<UserOutDTO> ssoLogin = memberServiceFeign.ssoLogin(userLoginInpDTO);
if (!isSuccess(ssoLogin)) {
redirectAttributes.addAttribute("errorMsg", ssoLogin.getMsg());
redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "redirect:/login";
}
UserOutDTO data = ssoLogin.getData();
if (data == null) {
redirectAttributes.addAttribute("errorMsg", "用户信息数据为空!");
redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "redirect:/login";
}
(3)登陆接口
@Override
public BaseResponse<UserOutDTO> ssoLogin(@RequestBody UserLoginInpDTO userLoginInpDTO) {
// 1.验证参数
String mobile = userLoginInpDTO.getMobile();
if (StringUtils.isEmpty(mobile)) {
return setResultError("手机号码不能为空!");
}
String password = userLoginInpDTO.getPassword();
if (StringUtils.isEmpty(password)) {
return setResultError("密码不能为空!");
}
// 判断登陆类型
String loginType = userLoginInpDTO.getLoginType();
if (StringUtils.isEmpty(loginType)) {
return setResultError("登陆类型不能为空!");
}
// 目的是限制范围
if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
|| loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
return setResultError("登陆类型出现错误!");
}
// 设备信息
String deviceInfor = userLoginInpDTO.getDeviceInfor();
if (StringUtils.isEmpty(deviceInfor)) {
return setResultError("设备信息不能为空!");
}
// 2.对登陆密码实现加密
String newPassWord = MD5Util.MD5(password);
// 3.使用手机号码+密码查询数据库 ,判断用户是否存在
UserDo userDo = userMapper.login(mobile, newPassWord);
if (userDo == null) {
return setResultError("用户名称或者密码错误!");
}
return setResultSuccess(MeiteBeanUtils.doToDto(userDo, UserOutDTO.class));
}
/**
* SSO认证系统登陆接口
*
* @param userLoginInpDTO
* @return
*/
@PostMapping("/ssoLogin")
public BaseResponse<UserOutDTO> ssoLogin(@RequestBody UserLoginInpDTO userLoginInpDTO);
(4)shop-portal门户项目集成
application.yml:
#### 整合freemarker
spring:
freemarker:
cache: false
charset: UTF-8
check-template-location: true
content-type: text/html
expose-request-attributes: true
expose-session-attributes: true
request-context-attribute: request
suffix: .ftl
template-loader-path:
- classpath:/templates
application:
name: app-portal-pay
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
server:
port: 8050
redis:
hostname: 127.0.0.1
port: 6379
password: 123456
xxl-sso:
excluded:
paths:
xxl:
sso:
server: http://xxlssoserver.com:8080/xxl-sso-server
logout:
path: /logout
redis:
address: redis://127.0.0.1:6379
XxlSsoConfig:
@Configuration
public class XxlSsoConfig implements DisposableBean {
@Value("${xxl.sso.server}")
private String xxlSsoServer;
@Value("${xxl.sso.logout.path}")
private String xxlSsoLogoutPath;
@Value("${xxl-sso.excluded.paths}")
private String xxlSsoExcludedPaths;
@Value("${xxl.sso.redis.address}")
private String xxlSsoRedisAddress;
@Bean
public FilterRegistrationBean xxlSsoFilterRegistration() {
// xxl-sso, redis init
JedisUtil.init(xxlSsoRedisAddress);
// xxl-sso, filter init
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("XxlSsoWebFilter");
registration.setOrder(1);
registration.addUrlPatterns("/*");
registration.setFilter(new XxlSsoWebFilter());
registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);
registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);
registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);
return registration;
}
@Override
public void destroy() throws Exception {
// xxl-sso, redis close
JedisUtil.close();
}
}
在线properties转yml方式:
https://www.bejson.com/devtools/properties2yaml/
单点登陆系统设计思想都是一样
联合登陆都会遵循oatuh2.0协议
思考1:我们在项目中,如何集成xxl-sso框架?
肯定是修改xxl-sso框架?应该修改那些地方?
- 认证授权中心
需要介入微服务基础设施
修改登陆验证接口(调用会员数据库验证)
需要修改登陆界面
2.SSOClient集成
集成XxlSsoConfig使用XxlSsoWebFilter(注意排除请求)