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