文章目录
JWT
简介
它是一种token认证思想,由header,payload,Signature 组成。
header:
包括认证方式以及签名算法
payload:
保存的有效信息,如用户名,用户id等不敏感信息。
Signature:
签名,保证签名的信息没有篡改
优点
自包含:包含一些基础用户信息
简短:适合放在无状态的http协议里面
简单使用
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
生成token
String key = "token!Q2W#E$RW";
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 90);
//生成令牌
String token = JWT.create()
.withClaim("username", "张三")//设置自定义用户名
.withExpiresAt(calendar.getTime())//设置过期时间
.sign(Algorithm.HMAC256(key));//设置签名 保密 复杂
//输出令牌
System.out.println(token);
解析token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString()); 、// 存的是时候是什么类型,取得时候就是什么类型,否则取不到值。
System.out.println("过期时间: "+decodedJWT.getExpiresAt());
工具类
public class JWTUtils {
private static String TOKEN = "token!Q@W3e4r";
/**
* 生成token
* @param map //传入payload
* @return 返回token
*/
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,7);
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(TOKEN));
}
/**
* 验证token
* @param token
* @return
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token); // 如果验证通过,则不会把报错,否则会报错
}
/**
* 获取token中payload
* @param token
* @return
*/
public static DecodedJWT getToken(String token){
return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
}
设置拦截器
public class JWTInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
return true;
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("msg", "Token已经过期!!!");
} catch (SignatureVerificationException e){
map.put("state", false);
map.put("msg", "签名错误!!!");
} catch (AlgorithmMismatchException e){
map.put("state", false);
map.put("msg", "加密算法不匹配!!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", "无效token~~");
}
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
//配置器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtTokenInterceptor()).
excludePathPatterns("/user/**") // 放行
.addPathPatterns("/**"); // 拦截除了"/user/**的所有请求路径
}
}
常见异常信息
解密之后格式不正常或许过期就会报错
SignatureVerificationException: 签名不一致异常
TokenExpiredException: 令牌过期异常
AlgorithmMismatchException: 算法不匹配异常
InvalidClaimException: 失效的payload异常
SSO
概念
相当于微服务的主机上面有一个主机是负责下发token的,而其他的主机都调该主机的服务。只要密钥是一样的,那么token解析出来的就是一样的。全局注销token
sso-client(所有子系统都应该拥有)
- 拦截子系统未登录用户请求,跳转至sso认证中心
- 接收并存储sso认证中心发送的令牌
- 与sso-server通信,校验令牌的有效性
- 建立局部会话
- 拦截用户注销请求,向sso认证中心发送注销请求
- 接收sso认证中心发出的注销请求,销毁局部会话
sso-server
- 验证用户的登录信息
- 创建全局会话
- 创建授权令牌
- 与sso-client通信发送令牌
- 校验sso-client令牌有效性
- 系统注册
- 接收sso-client注销请求,注销所有会话
实现流程
1 .在子页面创建登录或许注册拦截,引导进入server系统,并发送系统标识(一般为系统域名)
2.在server页面进行登录,注册操作。生成cookie信息,返回token认证。
3.在跳转其他系统需要进行登录时候,传入token和系统标识到server系统,如果已经认证,则返回登录信息。
注解:sso提高统一的登录和注册页面,其他子系统可以不写注册和登录
具体实现<可忽略不看>
1.client创建拦截
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
if (session.getAttribute("isLogin")) {
chain.doFilter(request, response);
return;
}
*//跳转至sso认证中心*
res.sendRedirect("sso-server-url-with-system-url");
}
2.server创建拦截
同上
3. 进行登录认证
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req) {
this.checkLoginInfo(username, password);
req.getSession().setAttribute("isLogin", true);
return "success";
}
4.生成token
String token = UUID.randomUUID().toString();
注解:token存在redis里面,通常value为用户信息
5.client收到请求校验token
// 请求附带token参数
String token = req.getParameter("token");
if (token != null) {
// 去sso认证中心校验token
boolean verifyResult = this.verify("sso-server-verify-url", token);
if (!verifyResult) {
res.sendRedirect("sso-server-url");
return;
}
chain.doFilter(request, response);
}
6.server 处理子系统请求
7.client校验成功以后创建局部对话
if (verifyResult) {
session.setAttribute("isLogin", true);
}
8.注销过程
1.cilent 发送logout请求
//client
String logout = req.getParameter("logout");
if (logout != null) {
this.ssoServer.logout(token);
}
//server
@RequestMapping("/logout")
public String logout(HttpServletRequest req) {
HttpSession session = req.getSession();
if (session != null) {
session.invalidate();//触发LogoutListener
}
return "redirect:/";
}
2.server发送全局注销
sso认证中心有一个全局会话的监听器,一旦全局会话注销,将通知所有注册系统注销。
设计模式
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式原则
1)单一职责原则
该原则是针对类来说的,即一个类应该只负责一项职责。
2)里氏替换原则
所有引用基类的地方必须能透明地使用其子类的对象。
3)依赖倒转原则
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
4)接口隔离原则
客户端不应该依赖它不需要的接口;
应该接口I拆分成3个接口
5)迪米特法则(最少知道原则)
即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息。
6)开闭原则
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。
创建型模式
简单工厂模式
定义
定义了一个创建对象的类,由这个类来封装实例化对象的行为。
举例
面条有很多种类,面是抽象类,热干面是实现类,泡面也是实现类。每个类都有domain()方法,来生产出来。
Node=》PaomianNode
ReiganNode
在这里我们利用多态原理来接受工厂返回的值,Node.domain()。
Node NodeFactory.getNode(“paomian”)
在该方法里面可以使用switch来判断值,然后返回不同的对象
switch(type){
case:"paomian":return new paomainNode()
case:"reigan":return new reiganNode()
}
优点
(1)工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责
(2)客户端无需知道所创建具体产品的类名,只需知道参数即可
(3)也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点
(1)工厂类集中了所有产品的创建逻辑,职责过重,一旦异常,整个系统将受影响
(2)使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
(3)系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂。违背了开闭原则
(4)简单工厂模式使用了static工厂方法,造成工厂角色无法形成基于继承的等级结构。
总结
- 优点:只需要传入一个正确的参数,就可以获取你所需要的对象,而无需知道其细节创建。
- 缺点:工厂类的职责相对过重,增加新的产品,需要修改工厂类的判断逻辑,违背了开闭原则。
工厂模式
定义
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
在简单工厂模式上面,改变了工厂返回对象的逻辑。而是抽取了一个父类NodeFactory,再创建子类PaomianNodeFactory来impl。
NodeFactory=》
PaomianNodeFactory.createNode return new PaomianNode()
ReiganNodeFactory.createNode return new ReiganNode()
- 优点:用户只需要关系所需产品对应的工厂,无须关心创建细节;加入新产品符合开闭原则,提高可扩展性。
- 缺点:类的个数容易过多,增加复杂度;增加了系统的抽象性和理解难度。
抽象工厂
**定义:**定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。
是工厂模式的升级版本,面馆升级,开始卖炒饭了,这个时候,同时拥有了泡面味道的炒饭,热干面味道的炒饭。我们根据口味来分别工厂。首先是一个抽象工厂,拥有创建饭和面的方法。然后两个不同口味的工厂来实现抽象工厂。返回不同口味的面和饭。
Factory=>
Node CreateRich()
Rich CreateNode()
PaomianFactory imp Factory
ReiganFactory imp Factory
Node=》PaomianNode
ReiganNode
Rich=>
PaominaRich
ReiganRich
PaomianFactory=>
createNode reurn new PaomianNode
CreateRich return new PaomianRich
ReiganFactory=>
createNode reurn new ReiganNode
CreateRich return new ReiganRich
场景:
-
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
属于同一个品种,比如说 车轮胎,车壳可以组装成车,飞机轮胎,飞机壳可以组装成飞机
-
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
小米,vivo,热干与泡面
-
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
总结
**优点:**当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
**缺点:**产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
单例模式
有手就行
建造者模式
定义
封装一个复杂对象构造过程,并允许按步骤构造。
一般来说就是链式创建对象,其实这样创建对象的时候,就不需要关心传入参数的个数和顺序。
举例
1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"
2、JAVA 中的 StringBuilder
角色
Product =》
partA
partB
partC
Builder =》
buildPartA
buildPartB
buildPartC
build(A,B,C)
Builder build(Object obj) //链式创建对象
getResult :return Product
ConcreteBuilder imp Builder
Director
Director Director(Builder)
Product construct()
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
product.show();
举例:
在MP中链式创建对象,
建造者模式和工厂模式的区别
建造者:建造一个复杂的对象,更注重复杂对象的建造过程
抽象工厂模式:建造一个产品族,更注重族群
生成器模式构建对象的时候,对象通常构建的过程中需要多个步骤,就像我们例子中的先有主机,再有显示屏,再有鼠标等等,生成器模式的作用就是将这些复杂的构建过程封装起来。工厂模式构建对象的时候通常就只有一个步骤,调用一个工厂方法就可以生成一个对象。
例如
buildA().buildB().buildC().instance()
getInstance()
原型模式
通过复制现有实例来创建新的实例,无需知道相应类的信息。
简单地理解,其实就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一毛一样的新对象来使用;基本上这就是原型模式。关键字:Clone。
深拷贝和浅拷贝
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。clone明显是深复制,clone出来的对象是是不能去影响原型对象的
结构型模式
代理模式
主要用于方法增强(AOP,Mybatis.getMapper())
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
jdk动态代理
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
InvocationHandler需要实现invoke方法,在该方法里面proxy代表代理对象
4.通过代理调用方法
/**
* InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
* 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
* 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
Subject realSubject = new RealSubject();
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
String hello = subject.SayHello("jiankunking");
Proxy.newProxyInstance(MapperClass.getClassLoader(), MapperClass.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里写入代理代码
//组装Id
String methodName = method.getName();
String className = MapperClass.getName();
String id=className+"."+methodName;
//然后判断执行的啥方法
switch(className){
case className equal "selectOne" return selcetOne()
deaf return selcetlist()
}
}
});
cglib动态代理
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
使用
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
源码分析
通过获取class对象创建子类,并重写方法。最后利用多态输出
适配器模式
定义
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
**主要分为三类:**类的适配器模式、对象的适配器模式、接口的适配器模式。
类适配器模式
通过多重继承目标接口和被适配者类方式来实现适配。(类似于充电线的1拖3)
VGA和USB的适配(目标接口只能提供VGA接口,需要把USB接口转成VGA接口)。于是开发了一个USB二代。二代同时继承了一代的功能,另外实现了一个VGA接口。(类似于充电线的1拖3)
USB
USBImpl //(一代)
USBIMpl2(AdapterUSB2VGA) //usb二代
继承一代,实现VGA接口,实现preojection方法,再该方法里面调用usb.showPPt()
VGA
VGAImpl //(一代)
TV //产品使用
preojection //这个就是VGA的接口,在这个接口里面调用VGA.preojection方法
对象适配器模式
对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。
类似于显示器转接器
USB
USBImpl //(一代)
USBIMpl2(AdapterUSB2VGA) //usb二代
组合一个usb,实现VGA接口,实现preojection方法,再该方法里面调用usb.showPPt()
VGA
VGAImpl //(一代)
TV //产品使用
preojection //这个就是VGA的接口,在这个接口里面调用VGA.preojection方法
接口适配器模式
在对象适配器的基础上抽象了一层抽象类,该抽象类把不需要适配的方法全部实现但不做任何处理。实现类继承了抽象类以后就只需要专注于适配的方法开发
USB
USBImpl //(一代)
USBIMpl2(AdapterUSB2VGA) //usb二代 抽象类
内部组合一个usb,实现VAG接口
USBIMpl2Impl(AdapterUSB2VGAImpl) //usb二代 实现类
实现projection方法即可
VGA
VGAImpl //(一代)
TV //产品使用
preojection //这个就是VGA的接口,在这个接口里面调用VGA.preojection方法
装饰者模式
定义
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
car
普通car
奔驰car
奔驰carFoary imp car
get奔驰car(car car)
return this
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。
在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。
外观模式
定义
隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。
A
start
stop
B
start
stop
C
start
stop
Facade
start
a.start
b.start
c.start
111111111