目录
一、登录的常见方式
1、单一服务器模式
2、单点登录SSO(Single Sign On)模式
二、单点登录的几种实现方式
1、session广播机制
3、令牌(使用token)
三、JWT(Json web token)
1、JWT的介绍
(1)JWT头信息
(2)有效载荷
(3)签名哈希
2、JWT和session比较的优缺点
3、JWT的使用
(1)引入依赖
(2)使用JWT工具类
四、OAuth2
1、开放系统间授权的几种方式
2、分布式访问(单点登录)
使用session对象实现。
在登录成功后,把用户数据放到session里面
session.setAttribute("user", user);
当需要判断是否登录时,就从session里面取数据,如果可以获取到,就是已经登录了。
session.getAttribute("user");
早期单一服务器(例如只有一台Tomcat)常用这种用户认证模式。
缺点是 单点性能压力大,并且无法扩展。
在集群环境下,一个项目包括多个微服务,如果采用以往的单一服务器登录模式,那么,在一个服务器登录上后,当我们需要访问另一个微服务时,还要再次登录。
所谓单点登录,就是在一个微服务登录后,其他微服务上都无需再次登录即可访问。
例如,在百度百科登录账号后,当我们再访问百度文库、百度音乐、百度地图等百度服务时,都无需再次登录 ,这就是单点登录在起作用。
当在一个微服务上登录后,登录信息保存在session中,通过session复制,将登录信息复制到其他微服务中,这就是session广播机制。通过这种机制可以实现单点登录。
缺点是:假如微服务较多,那么session复制的次数也会增多,并且复制出的session内容都是重复的,这对资源是一种极大的浪费。所以这种方式用于早期实现单点登录,现在已经淘汰。
用户在任意模块登录之后,将数据保存到两个地方:
(1)保存到Redis中
在key中生成唯一的值(ip、用户id等);在value中保存用户数据
(2)将Redis生成的key值保存到cookie中
当访问项目中其他模块时,发送请求时将cookie带上,把cookie中的值到Redis中进行查询。如果能够找到,说明已经登录过;如果找不到,说明没有登录 。
用户在任意模块登录之后,按照一定规则生成保存用户数据的字符串,并将字符串通过cookie或者通过地址栏返回。
当用户访问其他模块时,在地址栏中带上生成的字符串。访问模块解析该字符串,根据字符串获取信息。如果能获取到用户信息,说明已经登录过;如果获取不到,说明没有登录。
我们都知道,token是按照一定规则生成的包含用户信息的字符串。JWT就是通用的token生成规则
JWT生成的字符串包含三部分:
JWT头部分是一个包含JWT元数据的JSON对象。
包含主题信息(用户信息)
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
JWT的优点:
(1)可扩展性好。应用程序分布式部署的情况下,session需要做多机数据共享,通常存放在Redis里面,而jwt不需要;
(2)无状态。jwt不在服务端存储任何状态。发出请求时,总会返回带有参数的相应,不会产生附加影响;
(3)可以降低服务器查询数据库的次数。
JWT的缺点:
(1)安全性。由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全;
(2)性能。jwt太长,性能开销大得多;
(3)一次性。想要修改里面的内容,必须重新签发一个jwt。
io.jsonwebtoken
jjwt
/**
* @author helen
* @since 2019/10/16
*/
public class JwtUtils {
public static final long EXPIRE = 1000 * 60 * 60 * 24;
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "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 claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
OAuth2是针对特定问题的一种解决方案。
OAuth2主要用于解决两个问题:
(1)开放系统授权
(2)分布式访问(单点登录)
(1)直接给用户名密码
(2)通用开发者key(适用于合作商之间)
(3)办法令牌
接近OAuth2的方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议。因此就出现了OAuth2协议
登录成功之后,按照一定规则生成包含用户信息的字符串,然后将生成的字符串通过路径或者cookie传递。后面再发送请求时,每次都带着字符串进行发送,然后从字符串中获取用户信息。
这就是OAuth2解决方案:令牌机制,按照一定规则生成包含用户信息的字符串。
OAuth2并没有规定我们使用何种规则,比如我们可以使用JWT。