由于本人对于安全这块一知半解,所以简单的学习一下。
JWT的简介:
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT(全称:JSON Web Token),在基于HTTP通信过程中,进行身份认证。
JWT的构成:
JWT是由三段信息构成的,将这三段信息文本用.连接一起就构成了JWT字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
JWT的优点:
1.简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。
基于Token的身份认证 与 基于服务器的身份认证
基于服务器的身份认证方式存在一些问题:
- Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
- Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
- CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
- CSRF : 用户很容易受到CSRF攻击。
CSRF :一般指跨站请求伪造。
JWT与Session的差异
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
主要流程如下:
- 用户携带用户名和密码请求访问
- 服务器校验用户凭据
- 应用提供一个token给客户端
- 客户端存储token,并且在随后的每一次请求中都带着它
- 服务器校验token并返回数据
注意:
- 每一次请求都需要token
- Token应该放在请求header中
- 我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *
用Token的好处
无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!
还有一点,token在一段时间以后会过期,这个时候用户需要重新登录。这有助于我们保持安全。还有一个概念叫token撤销,它允许我们根据相同的授权授予使特定的token甚至一组token无效。
JWT与OAuth的区别
OAuth2是一种授权框架 ,JWT是一种认证协议。
无论使用哪种方式切记用HTTPS来保证数据的安全性。
OAuth2用在使用第三方账号登录的情况(比如使用weibo, qq, github登录某个app)
JWT是用在前后端分离, 需要简单的对后台API进行保护时使用.(前后端分离无session, 频繁传用户密码不安全)
OAuth2是一个相对复杂的协议, 有4种授权模式, 其中的access code模式在实现时可以使用jwt才生成code, 也可以不用。
获取token
@Component public class TokenUtils { /** * 过期时间为一天 * TODO 正式上线更换为15分钟 */ private static final long EXPIRE_TIME = 24*60*60*1000; /** * token私钥 */ private static final String TOKEN_SECRET = "joijsdfjlsjfljfljl5135313135"; /** * 生成签名,15分钟后过期 * @param username * @param username * @return */ public static String createToken(String username) { try { Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); // 附带username信息 return JWT.create() .withClaim("username", username) //到期时间 .withExpiresAt(date) //创建一个新的JWT,并使用给定的算法进行标记 .sign(algorithm); } catch (Exception e) { return null; } } public static boolean verity(String token){ try { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); return true; } catch (IllegalArgumentException e) { return false; } catch (JWTVerificationException e) { return false; } } public static String getUsername(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("username").asString(); } catch (JWTDecodeException e) { return null; } } public static String sign(String username, String secret) { try { Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME); Algorithm algorithm = Algorithm.HMAC256(secret); // 附带username信息 return JWT.create() .withClaim("username", username) .withExpiresAt(date) .sign(algorithm); } catch (Exception e) { return null; } } }
登录返回token
@ApiOperation(value = "登录",notes="登录验证") @PostMapping(value = "/api/login") public Map<String, Object> login(@RequestBody User user) { Map<String, Object> map = new HashMap<>(); // 对 html 标签进行转义,防止 XSS 攻击 String username = user.getUsername(); username = HtmlUtils.htmlEscape(username); ResponseDTO<Boolean> users = userService.login(username, user.getPassword()); //返回token String token = TokenUtils.sign(username, user.getPassword()); System.out.println(token); if (null == user) { map.put("code", "403"); map.put("message","认证失败"); return map; } else { //session.setAttribute("user", users); if (!StringUtils.isEmpty(token)) { if (token != null){ map.put("code", "200"); map.put("message","认证成功"); map.put("token", token); return map; } map.put("code", "403"); map.put("message","认证失败"); return map; } } map.put("code", "403"); map.put("message","认证失败"); return map; }