Day-17
单点登录的实现
实现单点登录的前台是搭建好zookeeper集群
参照搭建文档
https://gitee.com/chenzunxi/csdn-file/blob/master/zookeeper%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3.docx
1.JT-SSO系统搭建
1.1 编辑User POJO![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b7b6048469342362068a3c4a08c2dc90.png)
1.2 创建JT-SSO项目
1.2.1 新建项目
1.2.2 添加继承/依赖/插件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jt</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jt-sso</artifactId>
<dependencies>
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--添加插件 有main方法时 需要添加插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2.3 编辑结构代码![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c5964d3f84be12397bed26b6bfb1492b.png)
1.2.4 编辑UserController
完成jt-sso案例用户访问测试.![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/eb6117ca60281ef731b964df654349f7.png)
1.2.5 编辑UserService
编辑Service 完成用户信息获取![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/2a1d2e8c2e5185555dc3ccdc20ef2da7.png)
1.2.6 配置nginx/hosts
要求: 通过sso.jt.com的方式访问8093服务器.
1.编辑hosts文件
2.编辑Nginx配置文件
3.测试效果
2.用户数据校验
2.1 跨域页面分析
2.1.1 url分析
2.1.2 JS分析
检索页面JS
2.2 用户接口文档说明
2.3 编辑JT-SSO UserController在这里插//要求返回的数据都是JSON
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 完成用户数据的校验 JT-SSO项目
* url地址: http://sso.jt.com/user/check/{param}/{type}
* 参数: param 校验数据/type 校验类型/callback 回调函数
* 返回值: SysResult VO对象 data:true数据存在/false 数据不存在可以使用
* 提示: 该请求是JSONP请求方式,,所以需要特定的格式封装...
*/
@RequestMapping("/check/{param}/{type}")
public JSONPObject checkUser(@PathVariable String param,@PathVariable Integer type, String callback){
boolean flag = userService.checkUser(param,type); //表示数据不存在,可以使用
SysResult sysResult = SysResult.success(flag);
return new JSONPObject(callback, sysResult);
}
2.4 编辑JT-SSO UserService
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService{
private static Map<Integer,String> columnMap = new HashMap<>();
static {
columnMap.put(1, "username");
columnMap.put(2, "phone");
columnMap.put(3, "email");
}
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.selectList(null);
}
//如果数据库中存在 返回true 不存在false
//如何判断 数据库中是否存在
//type=1 username type=2 phone type=3 email
@Override
public boolean checkUser(String param, Integer type) {
//String column = type==1?"username":(type==2?"phone":"email");
String column = columnMap.get(type);
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq(column,param);
int count = userMapper.selectCount(queryWrapper);//0|1
//return count>0?true:false;
return count>0;
}
}
2.5 关于全局异常处理
package com.jt.aop;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.jt.vo.SysResult;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
//定义全局异常的处理机制
@RestControllerAdvice
public class SystemAOP {
/**
* 业务说明: 由于jsonp跨域调用即使错误,也应该按照callback(JSON)的方式返回
* 所以应该重构全局异常处理.,关于JSONP返回的问题
* 思路: 由于jsonp调用一般都会携带callback参数 所以可以通过该参数是否存在判断是否为JSONP调用!!!
* @param
* @return
*/
@ExceptionHandler({RuntimeException.class})
public Object result(Exception e, HttpServletRequest request){
e.printStackTrace(); //打印错误日志
String callback = request.getParameter("callback");
if(StringUtils.hasLength(callback)){
//如果callback有值 按照jsonp方式返回
return new JSONPObject(callback, SysResult.fail());
}
return SysResult.fail();
}
}
2.6 编辑页面JS
3.HttpClient介绍
3.1 远程调用分析
3.2 HttpClient介绍
HTTP 协议是 Internet 上使用得最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。Commons HttpClient项目现已终止,不再开发。 它已被Apache HttpComponents项目里的HttpClient和HttpCore模块取代,它们提供了更好的性能和更大的灵活性。 [1]
3.3 HttpClient入门案例
3.3.1 编辑POM.xml文件
<!--添加httpClient jar包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
3.3.2 入门案例
package com.jt;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class TestHttpClient {
/**
* 1.实例化HttpClient对象
* 2.定义url地址
* 3.封装请求方式GET/POST/PUT....
* 4.发送请求获取响应的结果.response
* 5.判断响应是否正确. 200表示请求正确,获取响应的结果.
* 6.解析服务器返回值.获取有效数据
*/
@Test//在java代码中发起http请
public void testGet() throws IOException {
HttpClient httpClient = HttpClients.createDefault();
String url = "https://www.cctv.com/";
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
int status = httpResponse.getStatusLine().getStatusCode(); //获取状态码
if(status == 200 ){
HttpEntity entity = httpResponse.getEntity(); //获取响应的实体对象
String result = EntityUtils.toString(entity, "UTF-8");
System.out.println(result);
}
}
}
3.3.3 关于HttpClient 案例2
需求: 用户通过浏览器网址http://www.jt.com/user/findAll请求,要求获取jt-sso中的所有的用户信息.
提示: jt-web服务器没有办法直接访问数据库…
jt-sso服务器可以访问数据…
jt-web向jt-sso发起请求为http://sso.jt.com/user/findAll
3.3.4 关于httpClient和JSONP说明
面试题:
问: HTTPClient是跨域请求吗? 不是 就是远程过程调用…
4.微服务思想(服务小型化)
4.1 SOA思想
面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
核心理念: 服务松耦合的思想…
4.2 RPC
RPC是远程过程调用(Remote Procedure Call)的缩写形式。
漫话RPC:
一个阳光明媚的早晨,老婆又在翻看我订阅的技术杂志。
“老公,什么是RPC呀,为什么你们程序员那么多黑话!”,老婆还是一如既往的好奇。
“RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用嘛”,我一边看着书,一边漫不经心的回答着。
“啥?你在说啥?谁不知道翻译成中文是什么意思?你个废柴,快给我滚去洗碗!”
“我去。。。”,我如梦初醒,我对面坐着的可不是一个程序员,为了不去洗碗,我瞬间调动起全部脑细胞,星辰大海在我脑中汇聚,灵感涌现…
“是这样,远程过程调用,自然是相对于本地过程调用来说的嘛。”
“嗯哼,那先给老娘讲讲,本地过程调用是啥子?”
“本地过程调用,就好比你现在在家里,你要想洗碗,那你直接把碗放进洗碗机,打开洗碗机开关就可以洗了。这就叫本地过程调用。”
“哎呦,我可不干,那啥是远程过程调用?”
“远程嘛,那就是你现在不在家,跟姐妹们浪去了,突然发现碗还没洗,打了个电话过来,叫我去洗碗,这就是远程过程调用啦”,多么通俗易懂的解释,我真是天才!
“哦!我明白了”,说着,老婆开始收拾包包。
“你这是干啥去哦”
“我?我要出门浪去呀,待会记得接收我的远程调用哦,哦不,咱们要专业点,应该说,待会记得接收我的RPC哦!”
总结:
1.本地过程调用
完成业务时,如果可以调用自己的方法完成.称之为本地过程调用(自己调用自己的方法 同一个服务器中)
2.远程过程调用
完成业务时,自己的方法不能操作或者没有该功能时,需要调用别人的服务器(方法)来完成业务.(不同的服务器之间的调用)
RPC:不同的服务器之间的调用就是RPC
4.3 微服务的调用思想
微服务思想: 将程序(项目)按照分布式的思想进行拆分(SOA),并且可以自动的实现故障的迁移(服务自动发现机制),无需人为的干预
4.3.1 传统方式调用
说明:
1.按照常规的调用方式,每次增加/减少服务器时,都需要编辑conf配置文件. 没有发办法实现服务自动的发现(不够智能)
2.只要服务器进行RPC调用都必须经过nginx服务器,.则nginx压力很高
4.3.2 微服务调用思想![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/f347a452aa58f5a700217d03c2543485.png)
步骤:
1.服务启动时,会链接注册中心,将服务数据(服务名称|IP|端口)写入注册中心.
2.注册中心接收用户服务数据之后,动态维护服务列表.
3/4.消费者启动时,链接注册中心,之后将服务列表缓存到本地(缓存到消费者内存中(快)) 方便下次调用.
5.当用户调用服务消费者时.消费者根据当前服务列表的信息,进行负载均衡,挑选其中一个服务进行访问.
6.注册中心为了保证服务列表的正确性,通过心跳检测机制.实时监控所有服务生产者,如果服务器宕机,则注册中心将第一时间更新服务列表.并且全网广播 通知所有的消费者.更新服务列表.
优点:用户每次访问 几乎可以保证访问的服务器都是正确的.
5京淘项目Dubbo改造
5.1 导入jar包依赖
<!--引入dubbo配置 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
5.2在jt-common 定义以下中立接口
DubboCartService
DubboItemService
DubboOrderService
DubboUserService
5.3改造JT-MANAGE-----(服务提供者)
5.3.1定义实现类DubboItemServiceImpl
5.3.2编辑jt-manage YML配置文件
server:
port: 8091
servlet:
context-path: /
spring:
datasource:
#url: jdbc:mysql://192.168.126.129:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root #数据源按照自己的写
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
#关于Dubbo配置
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application: #应用名称
name: provider-item #一个接口对应一个服务名称
registry:
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20880 #每一个服务都有自己特定的端口 不能重复.
6 构建服务消费者-JT-WEB
6.1 编辑ItemController
6.2 编辑消费者 jt-web YML配置文件
server:
port: 8092
spring: #定义springmvc视图解析器
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
dubbo:
scan:
basePackages: com.jt
application:
name: consumer-user #定义消费者名称
registry: #注册中心地址
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
6.3SSO单点登录设计
SSO介绍
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的 [1]
6.3.1页面分析
2.参数说明
3.页面JS
7 编辑jt-web UserController
/**
*
* 业务需求: 完成用户登录操作
* URL地址: http://www.jt.com/user/doLogin?r=0.6659570464851978
* 请求参数: 用户名和密码
* 返回值: SysResult对象
*
* 知识点讲解:
* Cookie: 在客户端保存服务器数据,在客户端实现数据共享.
* cookie.setMaxAge(); cookie生命周期
* cookie.setMaxAge(0); 立即删除cookie
* cookie.setMaxAge(100); 设定100秒有效期 100秒之后自动删除
* cookie.setMaxAge(-1); 关闭会话后删除
* 2.设定path cookie的权限设定
* cookie.setPath("/") 一般条件下设定为/ 通用
* 权限:根目录及其子目录有效
* cookie.setPath("/user")
* 权限:/user目录下有效
* 3.设定Cookie资源共享
* cookie特点: 自己的域名下,只能看到自己的Cookie. 默认条件下不能共享的
* cookie.setDomain("jt.com"); 只有在xxx.jt.com的域名中实现数据共享
*/
@RequestMapping("/doLogin")
@ResponseBody
public SysResult doLogin(User user, HttpServletResponse response){
String ticket = dubboUserService.doLogin(user);
if(!StringUtils.hasLength(ticket)){
return SysResult.fail();
}
Cookie cookie = new Cookie("JT_TICKET", ticket);
cookie.setMaxAge(7*24*60*60); //设定7天有效
cookie.setPath("/"); //请求在根目录中都可以获取cookie
cookie.setDomain("jt.com");
response.addCookie(cookie);
return SysResult.success();
}
编辑UserService
/**
* 业务说明: 实现用户单点登录操作
* 1.根据用户名和密码查询数据库
* @param user
* @return
*/
@Override
public String doLogin(User user) {//username/password不为null
//密文加密
String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass);
//条件构造器 根据对象中不为null的属性充当where条件 查询的是user的全部信息
User userDB = userMapper.selectOne(new QueryWrapper(user));
String ticket = null;
if(userDB !=null){
//用户名和密码正确
ticket = UUID.randomUUID().toString().replace("-", "");
//数据安全性 没有办法得到保证 要对敏感数据进行脱敏处理
userDB.setPassword("123456你猜猜?");
String json = ObjectMapperUtil.toJSON(userDB);
jedisCluster.setex(ticket, 7*24*60*60, json);
}
return ticket;
}
页面效果展现,出现自己设定的key则成功
总结
-实现步骤
搭建所需环境,完成测试
1-先在jt-common 设计 User POJO
2-创建jt-sso项目,添加依赖,创建UserController/UserMapper/UserService/UserServiceImpl/SpringBootRun
3-编辑UserController,完成jt-sso案例用户访问测试,编辑Service 完成用户信息获取
4-配置nginx/hosts–>要求: 通过sso.jt.com的方式访问8093服务器.–>完成测试,访问sso.jt.com/findAll可以获取数据库所有用户信息
代码实现
1-当用户登录时,通过nginx访问jt-web中任意的服务器之后输入用户名和密码访问jt-sso单点登录服务器.
2-从前端把用户输入的信息获取到,然后查询数据库,业务层中校验用户名和密码是否正确,如果用户名和密码是正确的,将用户信息转化为JSON串后,之后生成加密的秘钥token(md5(盐值+随机数)),之后将token作为K,用户user的全部信息–userJSON作为V,并设定超时时间(一般为7天)保存到redis中,同时将token信息返回给客户端(jt-web)
3-jt-web 接收到redis服务端数据时,首先校验数据是否有效,如果数据准确无误,就将token信息写入到浏览器的Cookie中(4K).
4-当用户再访问的j-web时候,首先会先从Cookie中,获取用户的token的信息,之后查询redis缓存服务器,获取userJSON数据,之后将userJSON转化为User对象进行使用,实现免密登录,如果token数据为null,那么则让用户访问登录页面,重新登录.
重点难点
1-前端数据传过来的时候,和后端数据的如何作比对
控制层方法参数有三个,param–要校验的数据,type–校验类型,callback–回调函数
前端传过来的username,phone,email,在后端先封装好,放进一个HashMap中,与前端传来数据,到了业务层,调用selectCount,前端数据和集合中的数据相等,表明存在用户则返回1,不等表示用户不存在或者错误,返回0,
2-如何封装token返回给jt-web
第一次登录的时候,密码进行明文加密,调用API
DigestUtils.md5DigestAsHex(user.getPassword().getBytes());