使用cookie和session做单点登录的弊端
-
为了记录用户的信息方便用户下一次请求的鉴别,服务端会存一份用户的数据到session中,随着用户数量的增加服务端的开销会增大。
-
不同服务难以共享session中的数据,就算使用springsession的分布式session,也只能放大domin和子域名共享session而不能使不同域名的服务共享session。
-
因为是基于cookie进行用户识别的,cookie如果被截获,坏人就能拿着这个cookie对用户进行攻击
-
只有浏览器有cookie
使用jwt(Json Web Token)
jwt是啥
JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token。
结构:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。Header.Payload.Signature三段。
Header:指定签名使用的算法,令牌的类型(jwt)。
payload:中可以存储json信息,建议不要存敏感信息
Signature:使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret),该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)生成签名,在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
jwt的加密
为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:
- 对称加密(HMAC【哈希消息验证码】):
secretKey指加密密钥,可以生成签名与验签,签名和验证使用相同的密钥。性能高,秘钥泄露有安全问题。 - 非对称加密(RSASSA【RSA签名算法(非对称)】):签名使用私钥,验证使用公钥,如果不是经常修改可将公钥保存到前端。安全性高,私钥只保存在服务端,token传输只需要传公钥,公钥只用于验签判断是否被篡改,并不能解密。
优势
-
自包含,能将用户的信息存到jwt当中,如可以存入用户的id然后根据id再到redis当中查询到用户的具体信息,可以避免大量用户将数据存到session给服务端带来的压力。
-
不需要在session中保存信息所以适合分布式的系统
-
短小精悍传输速度快
-
jwt是以json的形式存在客户端的,所以是跨语言的
最佳实现
-
在登录服务登录之后给用户生成一个随机的token作为用户在reids的id(uuid),将这个随机的id作为键用户信息作为值存入redis并设置过期时间。
-
将生成的token作为jwt的payload生成jwt然后返回给前端,前端将这个jwt保存在客户端的localstorage。
-
之后每次前端发的请求都带上这个jwt放到请求头中给后端,后端定义一个拦截器,每次前端的请求经过拦截器都会解析出jwt中的token再根据token到redis中查得用户登录信息放入请求域中。
分布式商城中的应用
商城前台服务前后端不分离不使用localstorage
使用jwt+cookie,登录后在认证服务域名下存cookie,其他服务到认证服务获取cookie。
-
导入jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
-
编写jwt工具类用户jwt的生成和解析
public class JwtUtil {
//相当于盐
private static final String SING = "%F$#TYF%G123";
/**
* 生成token
* @param map 存入到payload的数据
* @return
*/
public static String getToken(Map<String,String> map){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR,1);//设置令牌过期时间一个小时
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//遍历map中的数据存入payload
map.forEach((k,v) -> {
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(calendar.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256(SING));//指定盐
return token;
}
/**
* 验证token,如果验证通过则返回衣蛾DecodedJWT对象可以在这个对象中获取token中的数据
* @param token
* @return
*/
public static DecodedJWT verity(String token){
//创建验证对象
JWTVerifier build = JWT.require(Algorithm.HMAC256(SING)).build();
//验证,如果出错会报异常
DecodedJWT verify = build.verify(token);
return verify;
/**
* 获取解析后的token中的payload信息
* Claim userId = verify.getClaim("userId");
* Claim userName = verify.getClaim("userName");
* System.out.println(userId.asInt());
* System.out.println(userName.asString());
*/
}
}
-
登录成功后将随机生成的token(uuid)作为键用户信息作为值存入redis,并使用JWTUtil将uuid生成JWT存入Auth-Server域名下的cookie。
-
给需要用户信息的服务编写拦截器,因为cookie不能跨域,所以在此服务下获取不到登录信息,因此我们在拦截器中重定向到Auth-Server服务获取jwt,获取到jwt后用JWTUtil解析出payload中的token再根据token到redis中查出用户信息放入请求域中,并且将jwt存到当前服务域名下的cookie中方便下一次该服务就不用重定向到Auth-Server中获取jwt了。
拦截器:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MemberEntity loginUser = null;
String token = null;
//查看在当前域名是否已经有token的cookie
Cookie[] cookies1 = request.getCookies();
for(Cookie c : cookies1){
if(c.getName().equals("token")){
token = c.getValue();
}
}
//如果当前域名没有token
if(StringUtils.isEmpty(token)){
//获取当前请求的地址
String serverName = "http://mymall.com";
String requestURI = request.getRequestURI();
String url = serverName + requestURI;
//如果当前域名没有token,就带上url去认证服务认证获取token
token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
response.sendRedirect("http://auth.mymall.com/auth" + "?url=" + url);
return true;
}
}
//第二次来到拦截器,如果token是“nothing”说明认证服务的域名下没有cookie即没有用户的登录信息,
// 则直接放行这样请求域中就没有用户的登录信息
if(!token.equals("nothing")){
//token不是nothing说明有cookie,则把用户信息放入请求域
try {
DecodedJWT verity = JwtUtil.verity(token);
//校验成功之后从jwt中获取payload中的用户id
String userId = verity.getClaim("userId").asString();
//再根据用户id到redis中找到对应的用户信息
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
String s = ops.get(userId);
loginUser = JSON.parseObject(s,MemberEntity.class);
//将token存入当前域名下的cookie
Cookie c = new Cookie("token", token);
response.addCookie(c);
//放入请求域中
request.setAttribute("loginUser",loginUser);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
Auth-server中的认证接口:
/**
* 认证(获取token)
* @param request
* @param token
* @return
*/
@GetMapping("/auth")
public String login(HttpServletRequest request, @CookieValue(value = "token",required = false) String token){
if(StringUtils.isEmpty(token)){
token = "nothing";
}
String url = request.getParameter("url");
return "redirect:" + url + "?token=" +token;
}
-
在页面中获取请求域中的登录用户的昵称
3091

被折叠的 条评论
为什么被折叠?



