单点登录的三种方式和JWT的介绍与使用

单点登录三种方式

单点登录的三种实现方式:

  • 分别为session广播机制;
  • cookie+redis;
  • token

session广播机制指在一个集群中的一个模块登录后,然后把该session复制成几份,发送到该集群的其他模块中,可以形成在个地方登录,其他地方不用再登录的效果。

session广播机制

参与集群的每个节点的Session状态都被复制到集群中的其他所有节点上,无论何时,只要Session发生改变,Session数据都要重新被复制。Tomcat、JBoss等都提供了这样的功能,其中Tomcat采用集群节点广播复制,JBoss采用配对复制机制。

Session广播机制优缺点:
优点:每个节点都复制一份Session,一个节点出现问题时其它节点可以接替它的工作。
缺点:节点间进行Session同步会占据大量系统资源,整体性能随着集群节点数的增加而下降。

cookie+redis方式

用户在项目的任意一个模块登录后,登录之后,该模块会将用户的登录信息放到两个地方:redis 和 cookie 中。

系统会先将用户的登录信息存入 redis中,其在 redis 的 key 值是生成的唯一值 (可以包含 ip、用户 id、UUID等),value 值存放用户的登录信息。
接着系统会将这名用户在 redis 中的 key 值存入该用户的 cookie 中,用户每次访问任意模块时都会带着这个 cookie。
用户在访问其他模块发送请求时,会带着客户端的 cookie 进行请求,而客户端的 cookie 已经存入了该用户在 redis 中的 key 值,这样其他模块在处理用户的请求时,可以先获取用户 cookie中的 key 值,然后拿着这个 key 值到 redis 中进行查询,如果在 redis 中能查询到该用户相应的登录信息,就说明该用户已登录,就不需要用户进行重复登录了。

token方式

token 是按照一定规则生成的字符串,字符串中可以包含用户信息。开发人员可以自行定制这个生成规则,也可以使用提供好的生成规则(如使用 JWT 自动生成包含用户信息的字符串)。

用户在项目的某个模块进行登录后,系统会按照一定(或定制)的规则生成字符串,把用户登录之后的信息包含到这个生成的字符串中,然后系统可以将这个字符串返回,主要有两种返回方式:

1.可以把字符串通过 cookie 返回;

2.可以把字符串通过地址栏返回。

这样用户在访问其他的模块时,每次访问的地址栏都会带着生成的字符串成字符串),然后根据字符串获取用户信息,如果可以获取到用户的登录信息,说明该用户已登录,用户就不需要重复登录了。

JWT令牌介绍

JWT生成的字符串包括三部分:
1.JWT头信息
2.有效载荷(包含头部信息)
3.签名哈希

JWT头

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{
“alg”: “HS256”,
“type”: “JWT”
}

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);type属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

有效载荷

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择:

  • iss:发行人
  • exp:到期时间
  • sub:主题
  • aud:用户
  • nbf:在此之前不可用
  • iat:发布时间
  • jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{
 "sub": "1234567890",
 "name": "xiaowei",
 "admin": true
}

默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。

签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。

JWT如何使用

JWT的原则

JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。

{
 "sub": "1234567890",
 "name": "xiaowei",
 "admin": true
}

之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。

服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。

JWT的用法

客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。

此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。

JWT的用处

JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库 存储在客户端,不占用服务端的内存资源
JWT默认不加密,但可以加密。生成原始令牌后,可以再次对其进行加密。 当JWT未加密时,一些私密数据无法通过JWT传输。
JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。
JWT本身包含认证信息,token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。
为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

整合JWT令牌

首先在项目中添加依赖:

<dependencies>
 <dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 </dependency>
</dependencies>

创建JWT的工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; 
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

public class JwtUtils {
 public static final long EXPIRE = 1000 * 60 * 60 * 24;
 public static final String APP_SECRET = "zkdlh8Ycvxmwd9sDxzc8czxcascX9";
 
 public static String getJwtToken(String id, String nickname){
 
 String JwtToken = Jwts.builder()
 
 .setHeaderParam("type", "JWT")
 
 .setHeaderParam("alg", "HS256")
 
 .setSubject("guli-user")
 
 .setIssuedAt(new Date())
 
 .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
 
 .claim("id", id)
 
 .claim("nickname", nickname)
 
 .signWith(SignatureAlgorithm.HS256, APP_SECRET)
 
 .compact();
 
 
 return JwtToken;
 
 }
 
 /**
 
 * 判断token是否存在与有效
 
 * @param jwtToken
 
 * @return
 
 */
 
 public static boolean checkToken(String jwtToken) {
 
 if(StringUtils.isEmpty(jwtToken)) return false;
 
 try {
 
 Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
 
 } catch (Exception e) {
 
 e.printStackTrace();
 
 return false;
 
 }
 
 return true;
 
 }
 
 /**
 
 * 判断token是否存在与有效
 
 * @param request
 
 * @return
 
 */
 
 public static boolean checkToken(HttpServletRequest request) {
 
 try {
 
 String jwtToken = request.getHeader("token");
 
 if(StringUtils.isEmpty(jwtToken)) return false;
 
 Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
 
 } catch (Exception e) {
 
 e.printStackTrace();
 
 return false;
 
 } 
 return true; 
 }
 
 
 /**
 
 * 根据token获取id
 
 * @param request
 
 * @return
 
 */
 
 public static String getMemberIdByJwtToken(HttpServletRequest request) {
 
 String jwtToken = request.getHeader("token");
 
 if(StringUtils.isEmpty(jwtToken)) return "";
 
 Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
 
 Claims claims = claimsJws.getBody();
 
 return (String)claims.get("id");
 
 }
 
}
作者:小威要向诸佬学习呀原文地址:https://blog.csdn.net/qq_53847859/article/details/126820882

%s 个评论

要回复文章请先登录注册