4.基础技术&API网关&JWT

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 4.基础技术&API网关&JWT

基础技术

自定义拦截器-HandlerInterceptor

一般情况下,对来自浏览器的请求,在该请求进行业务逻辑处理之前,我们是可以进行拦截处理的,实 现的方式有两种,分别是基于Tomcat的Filter,和基于SpringMVC的HandlerInterceptor。

  • 基于Tomcat的Filter实现拦截,可以利用SpringBoot的FilterRegistrationBean实现Filter和Spring 的整合,向Tomcat注册自定义Filter,使Filter可以在Servlet处理请求之前拦截请求。
  • 基于SpringMVC中的拦截器HandlerInterceptor实现拦截器,拦截器可以在Controller中相应的 Action方法处理请求前进行拦截
Filter和Interceptor的区别:
1. Filter直接依赖Servlet容器,而Interceptor不直接依赖于Servlet容器
2. Filter对Http请求起作用,而Interceptor只能对action请求起作用。
3. Interceptor可以访问Action的上下文值栈里的对象,即接收的参数,而Filter不能。
4. 在action的生命周期里,Interceptor可以被多次调用,而Filter针对一个请求只会被调用一次。
5. Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、 modelAndView、exception进行操作

代码实现

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行了preHandle");
        return false;
    }
}
@Configuration
public class MyWebMvcConfiure implements WebMvcConfigurer {
    @Autowired
    MyInterceptor myInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry
                .addInterceptor(myInterceptor)
                .addPathPatterns("/**");
    }
}

请求的拦截流程:

TK-Mybatis

官网:https://github.com/abel533/Mapper/wik

TK-Mybatis是在Mybatis的基础之上进行了简化开发,主要针对单表的查询更方便了

使用步骤:

  • 导入依赖
<!--Tk-Mybatis相关-->
<!--Mysql连接-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<!--durid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.16</version>
</dependency>
<!--tk-Mybatis-->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
</dependency>
  • 数据源配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

这里在配置的时候一定要注意带上时区的信息,否则会报错

  • 代码
  • DO类
@Data
@Table(name = "user") //设置该类对应的表名
public class User {
    @Id //标记为主键
    @KeySql(useGeneratedKeys = true) //开启插入返回主键
   public Integer id;
    @Column(name = "username") //当类中的属性名和表中的字段对不上时,使用Column注解来转换
   public String name;
   public String mobile;
}
  • 接口
public interface UserMapper extends Mapper<User> {
}
  • SpringBoot的启动类
@SpringBootApplication
@MapperScan(basePackages = "com.fh.tkmybatis.tk") //扫描mapper包配置,注意要使用tk-mybatis的
public class TkMybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(TkMybatisApplication.class, args);
    }
}
  • 使用
@Test
    public void testInsert(){
//        User user = new User();
//        user.setMobile("1234567");
//        user.setName("whwu");
//        userMapper.insert(user);
        User user = new User();
        user.setName("zl");
        userMapper.insertSelective(user);
    }
    @Test
    public void query(){
//        User user = userMapper.selectByPrimaryKey(6);
//        System.out.println(user);
//        User user = new User();
//        user.setId(7);
//        User user1 = userMapper.selectByPrimaryKey(user);
//        System.out.println(user1);  
//        Example example = new Example(User.class);
//
//         example.createCriteria().andEqualTo("name", "zl");
//
//        List<User> users = userMapper.selectByExample(example);
//
//        System.out.println(users);
        Example example = new Example(User.class);
        example.createCriteria().andLike("name","%z%");
        List<User> users = userMapper.selectByExample(example);
        System.out.println(users);
    }
    @Test
    public void  testUpdate(){
        User user = new User();
        user.setName("景天");
        Example example = new Example(User.class);
        example.createCriteria().andEqualTo("mobile","1234");
        userMapper.updateByExampleSelective(user,example);
    }
    @Test
    public void testDelete(){
        Example example = new Example(User.class);
        example.createCriteria().andEqualTo("name","景天");
        userMapper.deleteByExample(example);
    }

MapStruct

在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之 后, DO一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO用于对象传输,也即 是 DO对象 对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。

DTO : Data Transport Object (数据传输对象)

VO: View Object (视图解析对象)

  • 导入依赖
<!--mapstruct-->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.3.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.3.0.Final</version>
</dependency>
  • 定义转换器接口
@Mapper(componentModel = "spring")
public interface UserConverter {
    @Mappings({
            @Mapping(source = "name",target = "username")
    })
    UserDTO usertoUserDTO(User user);
    List<UserDTO> usersToUserDTOList(List<User> users);
}
  • 同样的,如果这里出现了DO类和DTO类的属性名称对不上,那么是可以使用@Mappings注解来进行转换的,如果定义了单个对象的转换的方法上加上了注解,那在List的方法上就不需要加上注解了
@Data
public class UserDTO {
    public Integer id;
    public String username;
}
  • 使用定义好的转换器
@Test
public void testMapStruct(){
    Example example = new Example(User.class);
    example.createCriteria().andLike("name","%h%");
    List<User> users = userMapper.selectByExample(example);
    System.out.println(users);
    List<UserDTO> userDTOS = userConverter.usersToUserDTOList(users);
    System.out.println(userDTOS);
}

SPI

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用 它来做接口实现类的扩展发现(Dubbo、JDBC等), 简单来说,它就是一种动态替换发现的机制, 举个例子来 说, 有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。

Java SPI的机制的约定

  • 当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录中同时 创建一个以服务接口命名的文件。
  • 该文件里就是实现该服务接口的具体实现类。
  • 而当程序运行时,使用这个接口的实现类的时,就能通过该jar包META-INF/Services/的配置 文件找到具体的实现类名。
  • 并加载该实现类(可能有多个)并实例化 基于这样的一个约定就能很好的找到服务接口的实现类,而不需要在代码里指定。

具体的代码实现:

  • 首先定义接口
public interface Pay {
    void payAccount(Double money);
}
  • 定义接口实现类
public class AliPay implements Pay {
    @Override
    public void payAccount(Double money) {
        System.out.println("支付宝支付"+money+"元");
    }
}
public class WechatPay implements Pay {
    @Override
    public void payAccount(Double money) {
        System.out.println("微信支付"+money+"元");
    }
}

配置文件的名字需要和接口的全限定类名一致,里面的内容填写接口的实现类的全限定类名即可

com.fh.tkmybatis.spi.WechatPay
com.fh.tkmybatis.spi.AliPay
  • 使用过程
public class Test {
    public static void main(String[] args) {
        ServiceLoader<Pay> load = ServiceLoader.load(Pay.class);
        Iterator<Pay> iterator = load.iterator();
        while (iterator.hasNext()){
            Pay next = iterator.next();
            next.payAccount(200.0);
        }
    }
}

Gateway&JWT

API网关

概述

API是Application Programming Interface缩写,翻译成中文就是应用程序接口。在实际微服务中可以 理解一个个功能方法。就比如你一个用户服务的微服务,可以对外提供 API 接口为,查找用户,创建用 户等。

网关:

在计算机网络中,网关(英语:Gateway)是转发其他服务器通信数据的服务器,接收从客户端发送来的请求时,它就像自己拥有资源的源服务器一样对请求进行处理

如果没有网关,难道不行吗?功能上是可以的,我们直接调用提供的接口就可以了。那为什么还需要网 关?

因为网关的作用不仅仅是转发请求而已。我们可以试想一下,如果需要做一个请求认证功能,我们可以 接入到 API 服务中。但是倘若后续又有服务需要接入,我们又需要重复接入。这样我们不仅代码要重复 编写,而且后期也不利于维护。

由于接入网关后,网关将转发请求。所以在这一层做请求认证,天然合适。这样这需要编写一次代码, 在这一层过滤完毕,再转发给下面的 API。 所以 API 网关的通常作用是完成一些通用的功能,如请求认证,请求记录,请求限流,黑白名单判断 等。

API网关是一个服务器,是系统的唯一入口。

API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有 的非业务功能。通常,网关也是提供REST/HTTP的访问API。

架构图

JWT

简介

JWT全称 Json·Web·Token,是一个开放标准(RFC·7519),它定义了一种紧凑的,自包含的方式,用 于作为JSON对象在各方之间安全的传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT是目前最流行的跨域身份解决方案。

使用场景

下列场景中使用JWT是很有用的:

  • Information Exchange(信息交换):对于安全的在各方之间传输信息而言,Json·Web·Token 无疑是一种很好的方式。因为JWT可以被签名,我们还可以验证内容有没有被篡改。
  • 正因为数据可以基于JWT进行安全的传输,所以基于JWT,我们可以实现单点登录功能
单点登录:Single Sign On,简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在
多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录问题及解决方案

方案1:Nginx负载均衡做iphash,同一个用户的请求永远只给到同一个后台服务器。

不足之处:

  • 一个服务有若干个用户,有的用户活跃,向后端发起请求的次数比较频繁,而有的用户不活跃,那 么基于这种iphash的解决方案的话就势必会造成负载不均衡,也就是说有的tomcat服务服务器压 力大,而有的tomcat服务器压力不大,造成资源分配不均的情况。
  • 其次就是假如有一个tomcat服务挂了,那么对于有一些用户来说,这个系统就挂了,失去了集群 的意义。

方案2:基于tomcat广播的session复制

不足之处:

  • 每一个tomcat都需要维护一个大的session,会造成内存资源紧张。
  • 大量复制session占用服务器带宽

方案3: 将session数据,以能够共享的方式存储,比如存储到Mysql,或者Redis数据库中

  • 存储session数据时,还要多访问一次数据库,或涉及到网络,或IO过程

解决方案:使用JWT能够完美的解决上述问题

基于Token的身份认证,大概流程如下:

1.客户端使用用户名、密码登陆

2.服务器接收到请求后去验证用户名和密码

3.验证成功之后服务端会签发一个token,并且将这个token发送给客户端

4.客户端接收到了token之后将他存储起来

5.客户端每次向服务器发起请求的时候都需要带着服务器签发的token

6.服务端接收到请求,然后去验证客户端中的token,如果验证成功,就返回请求数据,否则不返回

Token VS Session

在看JWT如何解决上述问题之前,我们先来看看基于Token的身份认证与基于session的身份认证。

  • 基于session的身份认证。
  1. Sessions:每次用户认证通过以后,服务器需要创建一条记录来保存用户信息,通常是在内 存中。那么随着认证通过的用户越来越多,服务器在这里的开销就会越来越大。
  2. Scalability:由于session是在内存中的,这就带来一些扩展性的问题。
  3. CSRF:用户很容易受到CSRF的攻击
CSRF:跨站请求伪造(早期的攻击方式)
假设一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后者
刚发布了一个具有Bob银行链接的图片信息。正常情况下,Bob点击图片链接访问银行,银行会让
Bob登录
假如Bob在此之前刚好登录过了银行,浏览器中还有cookie,那么此时银行可能认为点击这个图片访问银行是Bob发出的,所以会正常给出响应。假如这个链接是一个Alice伪造的转账的请求,那么就会产生经济损失

对比

  1. 而JWT是把用户信息保存在客户端,基于token的方式将用户状态分散到了各个客户端中,可以减 轻服务端的内存压力。
  2. session的状态存储在服务端,客户端只有sessionId,而token的状态是存储在客户端,这就使得 服务器端并未存储用户登录状态,是无状态的,因为是无状态的,所以便于集群的扩容。每次在扩容的时候就不需要关心session的复制等问题
  3. 安全
  • Token不是cookie。每次请求的时候token都会被发送,可以作为请求参数发送,可以放在请 求头里面发送,也可以放在cookie里面被发送
  • 即使在你的实现中将token存储到客户端的cookie中,这个cookie也只是一种存储机制,而 非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话

JWT token的基本格式

JSON·Web·Token由三部分组成,他们之间用圆点(·)连接,这三部分分别是

  • Header
  • Payload
  • Signature

Header由两部分的信息组成:

  • type:声明类型,这里的是jwt
  • alg:声明加密的算法 通常直接使用 HMAC SHA256

Payload就是存放有效信息的地方(不强制)

iss: jwt签发者

sub: jwt所面向的用户

aud: 接收jwt的一方

exp: jwt的过期时间,这个过期时间必须要大于签发时间

nbf: 定义在什么时间之前,该jwt都是不可用的

iat: jwt的签发时间

jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

claim:jwt存放信息的地方

Signature:签名信息

因此,一个典型的JWT看起来是这个样子的: xxxxxxxx·yyyyyyyyyy·zzzzzzzzzzz

例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3bGd6cyIsImV4cCI6MTU4Nzk3MzY1NywidXNlciI6Ijk2MkYxODkwNTVFMzRFNzVERjVGMzQ0QTgxODNCODdGIn0.APehq9dxRiilgTOGyuz9qtZxvPDIJ5QIIVUCLYeX1QE

项目中如何使用

项目已经整合好了JWT,并且给我们提供了现有的工具类进行操作

com.mall.user.utils.JwtTokenUtils

如何使用现有的工具创建JWT呢?

String token = JwtTokenUtils.builder().msg("xxx").build().creatJwtToken();

如何解析JWT呢?

String msg = JwtTokenUtils.builder().token(token).build().freeJwt();

JWT的具体的执行流程

  • 首先在登陆的时候进行用户名密码验证,如果通过了那就通过加密算法存储信息后将token给客户端
  • 客户端之后访问网站的时候只需要携带token即可,然后服务器通过预存的解密算法进行解密,如果能正常的解密出来,那就说明是正确的token,即放行请求,否则不通过
  • 即使是多台服务器,只需要进行相同的方法解密就行了

TOGyuz9qtZxvPDIJ5QIIVUCLYeX1QE

### 项目中如何使用
项目已经整合好了JWT,并且给我们提供了现有的工具类进行操作

com.mall.user.utils.JwtTokenUtils

如何使用现有的工具创建JWT呢?
```java
String token = JwtTokenUtils.builder().msg("xxx").build().creatJwtToken();

如何解析JWT呢?

String msg = JwtTokenUtils.builder().token(token).build().freeJwt();

JWT的具体的执行流程

  • 首先在登陆的时候进行用户名密码验证,如果通过了那就通过加密算法存储信息后将token给客户端
  • 客户端之后访问网站的时候只需要携带token即可,然后服务器通过预存的解密算法进行解密,如果能正常的解密出来,那就说明是正确的token,即放行请求,否则不通过
  • 即使是多台服务器,只需要进行相同的方法解密就行了
目录
相关文章
|
1月前
|
JSON 安全 程序员
[JavaWeb]——JWT令牌技术,如何从获取JWT令牌
[JavaWeb]——JWT令牌技术,如何从获取JWT令牌
|
1天前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
27天前
|
Java Unix 关系型数据库
时间API在更新,传奇已经谢幕,但技术永远不死
时间API在更新,传奇已经谢幕,但技术永远不死
|
1月前
|
存储 安全 机器人
【LLM】智能学生顾问构建技术学习(Lyrz SDK + OpenAI API )
【5月更文挑战第13天】智能学生顾问构建技术学习(Lyrz SDK + OpenAI API )
68 1
|
1月前
|
JSON SpringCloudAlibaba Cloud Native
SpringCloudAlibaba:4.3云原生网关higress的JWT 认证
SpringCloudAlibaba:4.3云原生网关higress的JWT 认证
34 1
|
23天前
|
存储 安全 API
网络安全与信息安全:防御前线的关键技术深入理解RESTful API设计原则与实践
【5月更文挑战第30天】在数字化时代,网络安全和信息安全已成为维系信息社会运行的核心支柱。本文深入探讨了网络安全漏洞的概念、加密技术的进展以及提升安全意识的重要性。通过对这些领域的分析,旨在为读者提供一个关于如何保护个人和组织资产免遭网络威胁的综合性视角。 【5月更文挑战第30天】 在现代Web服务开发领域,表述性状态传递(REST)已成为构建后端API的一种流行且成熟的架构风格。本文将探讨RESTful API的核心设计原则,并通过实例分析如何将这些原则应用于实际开发中。我们将重点讨论资源的概念化、HTTP方法的正确使用、状态码的准确传达以及API的可扩展性和版本控制问题。通过本文,读者将
|
1月前
|
监控 负载均衡 网络协议
|
1月前
|
缓存 前端开发 搜索推荐
【Flutter前端技术开发专栏】Flutter中的自定义绘制与Canvas API
【4月更文挑战第30天】Flutter允许开发者通过`CustomPaint`和`CustomPainter`进行自定义绘制,以实现丰富视觉效果。`CustomPaint` widget将`CustomPainter`应用到画布,而`CustomPainter`需实现`paint`和`shouldRepaint`方法。`paint`用于绘制图形,如示例中创建的`MyCirclePainter`绘制蓝色圆圈。Canvas API提供绘制形状、路径、文本和图片等功能。注意性能优化,避免不必要的重绘和利用缓存提升效率。自定义绘制让Flutter UI更具灵活性和个性化,但也需要图形学知识和性能意识。
【Flutter前端技术开发专栏】Flutter中的自定义绘制与Canvas API
|
1月前
|
JSON 前端开发 Java
|
1月前
|
负载均衡 Cloud Native 安全
云原生最佳实践系列 6:MSE 云原生网关使用 JWT 进行认证鉴权
本文档介绍了如何在 MSE(Microservices Engine)云原生网关中集成JWT进行全局认证鉴权。

热门文章

最新文章