1、Redis+Lua脚本限流
1.1.定义切入点接口(小旗子),添加四个属性并赋初值,后续用到。
1.2.定义切面类,使用前置通知实现IP限流,其中
-
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature()----》获得修饰符+ 包名+组件名(类名) +方法名
-
Method method = methodSignature.getMethod()----》获取方法名
-
PugRateLimiter annotation = method.getAnnotation(PugRateLimiter.class)----》通过方法名上面的“小旗子”类,获取注解携带的限流参数
-
Integer limit = annotation.limit()----》获取限制次数 Integer timeout = annotation.timeout()----》获取限制时间
-
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ----》获取请求属性
-
HttpServletRequest request = requestAttributes.getRequest()----》获取请求对象
-
String methodNameKey = getRateLimiterKey(annotation, joinPoint, request)----》获取redis的key
-
String userIp = IpUtils.getIpAddr(request)----》获取请求IP
-
Boolean acquired = stringRedisTemplate.execute(ipLimitLua, Lists.newArrayList(methodNameKey), limit.toString(), timeout.toString())----》请求lua脚本实现限流,根据返回值进一步进行限流返回操作
方法 getRateLimiterKey ---- 获取Redis 的 key,最终Key返回值为:IP-声明的class对象名-方法名
stringRedisTemplate.execute(ipLimitLua, Lists.newArrayList(methodNameKey), limit.toString(), timeout.toString()) 方法,真正实现限流的方法
ipLimiLua----》用来读取lua脚本,序列化为java可以操作的语言
stringRedisTemplate.execute方法:用来执行lua脚本的,返回的是lua脚本执行之后的返回值。
2、JWT 实现免登录
2.1 JWT 介绍
jwt是为了解决传统session+cookie的登录校验的缺点。
传统的session+cookies的方法,session存储在服务端,这样客户端每次发起请求的时候只能请求同一个服务器,且将校验数据存储在服务器端会占用大量内存,不利于维护和扩展。而且cookie一旦被拦截,则用户很容易遭受跨站请求伪造的攻击。
而基于jwt token的鉴权机制,则是将认证信息(token)保存在客户端,服务端只需要保存用于加解密的secret(盐/密钥),每次客户端访问时,将首次访问服务端派发的token携带在请求头中,这样服务端拿到token,然后利用服务端存储的secret来进行解密,从而鉴权。服务器需要支持CORS(跨域资源共享)策略,以便于服务器密钥共享。
2.2 JWT组成
JWT 由三部分构成,分别是头部(header)、载荷(payload,类似于飞机上承载的物品)、签证(signature)。将这三部分用“ . ”连接起来,就构成了JWT字符串。
例如:
<pre>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U</pre>
header
header主要包含两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法,通常直接使用HMAC SHA256
完整的头部如:<pre>{ 'typ':'JWT', 'alg':'HS256' }</pre> ,然后经过based64加密(该加密是可以对称解密的),构成JWT第一部分:<pre>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9</pre>
payload
载荷是用来存放有效信息的地方,它主要包含三部分:
-
标准中注册的声明
-
iss:jwt签发者
-
sub:jwt所面向的用户
-
aud:接收jwt的一方
-
exp:jwt的过期时间,这个过期时间必须大于签发时间
-
nbf:定义在什么时间之前,该jwt都是不可用的
-
iat:jwt的签发时间
-
jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
-
-
公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其它业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密 。
-
私有的声明
私有的声明是提供者和消费者功能定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为名文信息。
payload:<pre>{ "sub": "1234567890", "name": "John Doe", "admin": true }</pre>,经过based64加密:<pre>eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9</pre>
Signature
签证是JWT的第三个部分,这个部分需要base64加密后的header和base64加密后的payload使用“.”连接组成字符串,然后用header中声明的加密方式,然后进行加secret加密。
如:
<pre>var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ</pre>
最终的JWT,就是由这三部分用“ . " 连接起来组成一个完整字符串:
<pre>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ</pre>
jwt 优点:
-
因为json的通用性,所以JWT是可以跨语言支持的,像C#,JavaScript,NodeJS,PHP等许多语言都可以使用
-
因为由了payload部分,所以JWT可以在自身存储一些其它业务逻辑所必要的非敏感信息
-
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
-
它不需要在服务端保存会话信息,所以它易于应用的扩展
注意点:
-
不应该在jwt的payload部分存储敏感信息,因为该部分是客户端可解密的部分
-
保护好secret私钥。该私钥非常重要
项目分析:
签发token过程:登录接口根据传入用户名来判断是否访问过,没有则派发token
token续期过程:
token
一般是在请求头里加入Authorization,并加上Bearer标注:
<pre>fetch('api/user/1', {
headers: { 'Authorization': 'Bearer ' + token
}
})</pre>
首先判断请求头中是否有token,token需要前缀Bearer
若请求头带有token则判断是否过期。