JWT 简介:
JWT(JSON web TOKEN):JSON 网络令牌, JWT 是一个轻便的安全跨平台传输格式, 定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON 格式). 它是在 Web 环境下两个实体之间传输数据的一项标准. 实际上传输的就是一个字符串. 广义上讲 JWT 是一个标准的名称; 狭义上 JWT 指的就是用来传递的那个 token 字符串
JWT 公司官网:
https://jwt.io/
jwt 的 token 结构:
JWT 的数据结构以及签发的过程
JWT 由三部分构成: header(头部),payload(载荷)和 signature(签名).
Header 头部信息: 指定类型和算法
Payload 荷载信息: 存放 Claims 声明信息
Signature 签名: 把前两者对应的 JSON 结构进行 base64url 编码之后的字符串拼接起来和密钥放一起加密后的签名
组成方式为: header.payload.signature
- :eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
- eyJpc3MiOiLlvKDlvLoiLCJuYW1lIjoiNzg5IiwiZXhwIjoxNTI4MzY0MjU5LCJpYXQiOjE1MjgzNjMwNTl9.574koY-c9SqMNNzfvAWQuKEnimWeZAcoFQ5XudNWF3o
因为是拼接的所以 base64 很容易破解, 所以你不要把密码或敏感的信息放入, 这样就算是被别人破解了你的荷载信息他也只能看着,
你该如何去理解 JWT 帮你做些什么?
结合业务场景, 首先前端登录 后端通过用户名与密码验证成功后, 使用 jwt 生成 一个具备有效期的 token 令牌, 这个 token 里面存储着 token 本身的结构, 加上 token 的过期时间, 和一个你自己定义的字符串类似于加密 签名所用的盐, 这个盐只有你自己知道, 这样你的加密才是安全的, 还可以包含一些自定义的用户信息放在荷载信息里,
本文需要你具备 javaSE 的基础, 所涉及的 jar 包如下:
- <!-- JWT javawebToken-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
- <!-- 阿里巴巴 json -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.4</version>
- </dependency>
jwt 如何生成 Token 呢?
注意: 这里使用的阿里巴巴提供的 JSON 工具包, 实现序列化, 后续会贴上所用的 jar
- /**
- * token 工具类
- * @author 郎俊楠
- *
- */
- public class JwtUtil {
- private static Logger logger = Logger.getLogger(JwtUtil.class);
- public static final String TOKEN_HEADER = "Authorization";//token 的 key 也是名 不要写成 token 这样, 要按照规范来
- public static final String TOKEN_PREFIX = "Bearer";//token 值的前缀, 这是一种规范 ok
- private static final String SECRET = "mrLang";// 你自己定的字符串 别让别人知道, 加密时候用 盐
- public static final String FUNCTS = "FUNCTS";// 获取用户的功能使用的 key
- public static final String USERINFO = "USER";// 获取用户使用的 key
- private static final long EXPIRATION = 1800L;// token 的生命周期 30 分
- /**
- * 创建 token 令牌 以下为参数都是自定义信息
- * @param loginName 一般我们放用户的唯一标识登录名
- * @param functs 当前用户的功能集合, 本人的 rbac 权限比较个性化且很负责, 一般你们放 role 角色就可以了
- * @param user 当前用户
- * @return
- */
- public static String createToken(String loginName, List<Object> functs, Users user) {
- Map<String, Object> map = new HashMap<>();
- // 当前用户拥有的功能
- map.put(FUNCTS, JsonUtil.set(functs));
- // 当前用户信息
- map.put(USERINFO, JsonUtil.set(user));
- String token = Jwts.builder()
- .setSubject(loginName)// 主题 主角是谁? 赋值登录名
- .setClaims(map)
- .setIssuedAt(new Date())// 设置发布时间, 也是生成时间
- .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))// 设置过期时间
- .signWith(SignatureAlgorithm.HS256, SECRET)// 设置 HS256 加密, 并且把你的盐 放里, 这里推荐使用 SH256 证书加密
- .compact();// 创建完成
- return token;
- }
jwt 如何验证 Token 是否过期呢?
- // token 是否过期
- public static boolean isExpiration(String token) {
- try {
- return getTokenBody(token).getExpiration().before(new Date());
- } catch (Exception e) {
- return true;
- }
- }
jwt 如何获取自定义的信息呢?
- // 获取主角, 登录名
- public static String getUserName(String token) {
- return getTokenBody(token).getSubject();
- }
- // 获取 token 中存储的功能
- public static List<Object> getUserFuncts(String token) {
- String str = getTokenBody(token).get(FUNCTS).toString();
- List<Object> list = JsonUtil.getArray(str);
- return list;
- }
- // 获取 token 存储的用户
- public static Object getUser(String token) {
- String str = getTokenBody(token).get(USERINFO).toString();
- return JsonUtil.getObj(str);
- }
- // 公共获取自定义数据
- public static Claims getTokenBody(String token) {
- return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
- }
jwt 如何刷新 Token 呢?
- // 刷新 token
- public static String refreshToken(String token) {
- if (isExpiration(token)) {
- logger.info("token 刷新失败!! 过期了!!");
- return null;
- }
- // 获取用户 权限信息
- String functs = getTokenBody(token).get(FUNCTS).toString();
- String user = getTokenBody(token).get(USERINFO).toString();
- String username = getTokenBody(token).getSubject();
- Map<String, Object> map = new HashMap<>();
- map.put(FUNCTS, JsonUtil.set(functs));
- map.put(USERINFO, JsonUtil.set(user));
- token = Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET).setClaims(map).setSubject(username)
- .setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
- .compact();
- return token;
- }
token 如何发送给前端?.
传入 当前用户的 功能与用户信息, 登录名 生成 token, 写入 response 的返回头中, 前端获取后保存在前端的本地缓存中, 后续前端请求要把 token 放在 头 header 里
- // 登录成功之后
- List<Object> functs=(List<Object>) authResult.getAuthorities();// 当前功能列表
- String loginName=authResult.getName();// 登录名
- Users obj=(Users)authResult.getPrincipal();// 用户信息
- String token=JwtUtil.createToken(loginName,functs,obj);// 生成 token
- //TOKEN_HEADER= Authorization TOKEN_PREFIX=Bearer token 值
- response.setHeader(JwtUtil.TOKEN_HEADER,JwtUtil.TOKEN_PREFIX+token);
- response.setContentType("application/json;charset=utf-8");
- response.setStatus(HttpServletResponse.SC_OK);
- // 个人编写的视图对象
- DTO dto=new DTO<>();
- dto.setCode("000000");
- dto.setMessage("认证通过");
- PrintWriter pw=response.getWriter();
- pw.write(JsonUtil.set(dto));// 写入 JSON
- pw.flush();// 强制刷新
- pw.close();// 关闭流
用户请求携带 token 如何验证?
你可以自定义一个过滤器, 或者使用某某框架, 自定义拦截器, 或者 aop
- String header = request.getHeader(JwtUtil.TOKEN_HEADER);
- if (null == header || !header.toLowerCase().startsWith(JwtUtil.TOKEN_PREFIX)) {
- // 如果头部 Authorization 未设置或者不是 basic 认证头部, 则当前
- // 请求不是该过滤器关注的对象, 直接放行, 继续 filter chain 的执行
- chain.doFilter(request, response);
- return;
- }
- try {
- String token = header.replace(JwtUtil.TOKEN_PREFIX, "");
- // 验证 token 是否过期
- if(JwtUtil.isExpiration(token)){
- throw new javax.security.sasl.AuthenticationException("token 验证不通过");
- }
- // 檢查 token 是否能解析
- Users user = (Users) JwtUtil.getUser(token);
- if (null == user) {
- throw new javax.security.sasl.AuthenticationException("token 验证不通过");
- }
- // 验证成功
总结:
使用 jwt 生成 一个具备有效期的 token 令牌, 这个 token 里面存储着 token 本身的结构, 加上 token 的过期时间, 和一个你自己定义的字符串类似于加密 , 也就是签名所用的盐, 这个盐只有你自己知道, 这样你的加密才是安全的, 还可以包含一些自定义的用户信息放在荷载信息里, 本文所用的 HS256 + 盐 对 token 加密是没有 RS256 安全的,
JWT 签名算法中 HS256 和 RS256 有什么区别?
JWT 签名算法中, 一般有两个选择, 一个采用 HS256, 另外一个就是采用 RS256.
签名实际上是一个加密的过程, 生成一段标识 (也是 JWT 的一部分) 作为接收方验证信息是否被篡改的依据.
RS256 (采用 SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共 / 私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名. 由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据 URL).
另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥. 由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密.
在开发应用的时候启用 JWT, 使用 RS256 更加安全, 你可以控制谁能使用什么类型的密钥. 另外, 如果你无法控制客户端, 无法做到密钥的完全保密, RS256 会是个更佳的选择, JWT 的使用方只需要知道公钥.
建议:
为了双重保险, 建议您使用 RS256, 最好是生成一个证书 读取使用证书里面的公钥加密, 私钥留着以后每次前端请求验证 token 的合法性就可以了
jwt 提供了方法可以验证 token 的合法性, 过期时间, 还可以根据 token 读取用户信息, 这样你把 token 存储前端的 localstorage 中,
后端的缓存都省了
这样做安全么?
你要知道 RS256 非对称加密是一套体系的, 公钥私钥是一组, 都存在的后端, 别人不进你服务器盗取你就是安全的, 要是真能登录你服务器那就直接删你库了,
来源: https://www.cnblogs.com/langjunnan/p/12464791.html