jwt身份令牌数据处理 前后端分离式开发

目录

1. jwt令牌的使用

1.1 什么是JWT?

1.2 什么是jwt令牌?

1.3 了解jwt的结构

1.4 JWT实际结构示例

2. jwt后台示例

3. 前台加入jwt功能


今天与大家分享jwt令牌控制用户登陆 浅浅的分享一波 后台与前台分离部分实例 

与小编对jwt的理解 和基本使用jwt入门 

如果内容 有问题 欢迎评价     最后 如果可以 谢谢大哥们点点浏览     觉得小编写的不错的 欢迎关注小编 这也才小编写的动力

1. jwt令牌的使用

1.1 什么是JWT?

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案

1.2 什么是jwt令牌?

简单来说,jwt令牌就是跨域身份验证完美解决方案,控制用户登陆的大门,判断用户是否合法。合法用户 则进行登陆; 不合法 不让用户登陆。

传统的intenet服务的认证是通过session进行的,当用户通过了安全认证后,则在服务端的session对象中保存该用户的认证信息,这样该用户对服务的访问被认为是安全的。

如果需要进行服务集群则需要处理好共享session的问题。 如果一个庞大的系统需要按服务分解为多个独立的服务,使用分布式架构,则这种方式更难处理。使用jwt可以方便的处理上面提到的问题。

1.3 了解jwt的结构

 

  1. JWT头:JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。{"alg": "HS256","typ": "JWT"}。
  2. 有效载荷:有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。这些声明被JWT标准称为claims。

例如:

{"sub":"123","name":"Tom","admin":true},sub代表这个token的所有人,存储的是所有人的ID;name表示这个所有人的名字;admin表示所有人是否管理员的角色。当后面对JWT进行验证的时候,这些claim都能发挥特定的作用。


1.4 JWT实际结构示例

  • 1、标头(Header)

标头通常由两部分组成︰令牌的类型(即 JWT )和所使用的签名算法,例如 HNAC SHA256 或 RSA 。它会使用 Base64 编码组成 JWT 结构的第一部分。

注意: Base64 是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

{
    "alg" : "HS256",
    "typ" : "JWT"
}

alg:加密算法 HS256:这是 JWT 推荐的也是默认的加密算法(签名算法)

  • 2、有效载荷(Payload)

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户信息)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

{
    "sub" : "1234567890",
    "name" : "John Doe",
    "admin" : true
}

注意:不要在 Payload 中存放用户的敏感信息(比如:密码);因为令牌的第一部分和第二部分都是用 Base64 编码的

  • 3、签名(Signature)

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过

如:

HIMACSHA256(base64UrlEncode(header) + “.” + base64Ur1Encode(payload) ,secret);

结构示图

 

签名目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。


信息安全问题

在这里大家一定会问一个问题: Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏﹐感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第﹐三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

2. jwt后台示例

后台流程:

1)将资料中src目录下的代码(CorsFilter.java,JwtFilter.java,JwtUtils.java)拷入项目的util目录下(可以根据项目设置的目录结构调整)。
2)程序的解释详见代码中的注释
3)在web.xml中配置JwtFilter过滤器
4)在用户登录验证成功后,需要为该用户生成jwt令牌,具体实现可参考UserAction.java
5)测试后台的验证服务,确定在验证通过的前提下可以正确的生成jwt令牌。

需要导入架包 使用maven 导入

 <!-- jwt 令牌需要架包  -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
    </dependency>
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.10.3</version>
    </dependency>

 

  • 1)(CorsFilter.java,JwtFilter.java,JwtUtils.java)拷入项目的util目录下(可以根据项目设置的目录结构调整)。

CorsFilter 作用域过滤器

package com.zking.vueserver.utils;
​
import java.io.IOException;
​
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * 配置tomcat允许跨域访问
 * 
 * @author Administrator
 *
 */
public class CorsFilter implements Filter {
​
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        // Access-Control-Allow-Origin就是我们需要设置的域名
        // Access-Control-Allow-Headers跨域允许包含的头。
        // Access-Control-Allow-Methods是允许的请求方式
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
        httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,jwt");
        //允许客户端,处理一个新的响应头jwt
        httpResponse.setHeader("Access-Control-Expose-Headers", "jwt");
        
        // axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
        if ("OPTIONS".equals(req.getMethod())) {
            return;
        }
        
        filterChain.doFilter(servletRequest, servletResponse);
    }
​
    @Override
    public void destroy() {
​
    }
}

 

  • JwtFilter 过滤器 发现没有jwt命令直接请求失效
package com.zking.vueserver.utils;
​
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
​
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
import io.jsonwebtoken.Claims;
​
/**
 * JWT验证过滤器,配置顺序 :CorsFilte-->JwtFilter-->struts2中央控制器
 * 
 * @author Administrator
 *
 */
public class JwtFilter implements Filter {
​
    // 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
    private static String EXCLUDE = "^/login";
​
    private static Pattern PATTERN = Pattern.compile(EXCLUDE);
​
    private boolean OFF = false;// true关闭jwt令牌验证功能
​
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
​
    @Override
    public void destroy() {
    }
​
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String path = req.getServletPath();
        if (OFF || isExcludeUrl(path)) {// 登陆直接放行
            chain.doFilter(request, response);
            return;
        }
​
        // 从客户端请求头中获得令牌并验证
        String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
        Claims claims = this.validateJwtToken(jwt);
        if (null == claims) {
            // resp.setCharacterEncoding("UTF-8");
            resp.sendError(403, "JWT令牌已过期或已失效");
            return;
        } else {
            String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
            resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
            chain.doFilter(request, response);
        }
    }
​
    /**
     * 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
     */
    private Claims validateJwtToken(String jwt) {
        Claims claims = null;
        try {
            if (null != jwt) {
                claims = JwtUtils.parseJwt(jwt);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }
​
    /**
     * 是否为排除的URL
     * 
     * @param path
     * @return
     */
    private boolean isExcludeUrl(String path) {
        Matcher matcher = PATTERN.matcher(path);
        return matcher.matches();
    }
​
}

 

JwtUtils 工具类  程序的解释详见代码中的注释

package com.zking.vueserver.utils;
​
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
​
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
​
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
​
/**
 * JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
 *
 */
public class JwtUtils {
    /**
     * JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
     */
    public static final long JWT_WEB_TTL = 30 * 60 * 1000;
​
    /**
     * 将jwt令牌保存到header中的key
     */
    public static final String JWT_HEADER_KEY = "jwt";
​
    // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
    private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key
​
    static {
        byte[] encodedKey = Base64.getDecoder().decode(JWT_SECRET);
        
        JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    }
​
    private JwtUtils() {
    }
​
    /**
     * 解密jwt,获得所有声明(包括标准和私有声明)
     * 
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJwt(String jwt) {
        Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(jwt).getBody();
        return claims;
    }
​
    /**
     * 创建JWT令牌,签发时间为当前时间
     * 
     * @param claims
     *            创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
     * @param ttlMillis
     *            JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
     * @return jwt令牌
     */
    public static String createJwt(Map<String, Object> claims, long ttlMillis) {
        // 生成JWT的时间,即签发时间
        long nowMillis = System.currentTimeMillis();
​
        // 下面就是在为payload添加各种标准声明和私有声明了
        // 这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                // 可以在未登陆前作为身份标识使用
                .setId(UUID.randomUUID().toString().replace("-", ""))
                // iss(Issuser)签发者,写死
                // .setIssuer("zking")
                // iat: jwt的签发时间
                .setIssuedAt(new Date(nowMillis))
                // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
                // .setSubject("{}")
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
                // 设置JWT的过期时间
                .setExpiration(new Date(nowMillis + ttlMillis));
​
        return builder.compact();
    }
​
    /**
     * 复制jwt,并重新设置签发时间(为当前时间)和失效时间
     * 
     * @param jwt
     *            被复制的jwt令牌
     * @param ttlMillis
     *            jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
     * @return
     */
    public static String copyJwt(String jwt, Long ttlMillis) {
        Claims claims = parseJwt(jwt);
​
        // 生成JWT的时间,即签发时间
        long nowMillis = System.currentTimeMillis();
​
        // 下面就是在为payload添加各种标准声明和私有声明了
        // 这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                // 可以在未登陆前作为身份标识使用
                //.setId(UUID.randomUUID().toString().replace("-", ""))
                // iss(Issuser)签发者,写死
                // .setIssuer("zking")
                // iat: jwt的签发时间
                .setIssuedAt(new Date(nowMillis))
                // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
                // .setSubject("{}")
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
                // 设置JWT的过期时间
                .setExpiration(new Date(nowMillis + ttlMillis));
        return builder.compact();
    }
}
​

  • 3)在web.xml中配置JwtFilter过滤器,但是配置跨域请求必须配置在jwt过滤器的前面
    <!-- 处理跨域请求 通过  -->
   <filter>
        <filter-name>CorsFilter</filter-name>
        <filter-class>com.zking.vueserver.utils.CorsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
​
    <!-- jwt -->
        <filter>
            <filter-name>JwtFilter</filter-name>
            <filter-class>com.zking.vueserver.utils.JwtFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>JwtFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

 

  • 4)在用户登录验证成功后,需要为该用户生成jwt令牌,具体实现可参考UserAction.java
  @RequestMapping("/login")
    @ResponseBody
    //接受json post请求 需要注解RequestBody
    public Object login( User us, HttpServletResponse response) {
        RetrunData re = null;
        if ("admin".equals(us.getUserName()) && "123".equals(us.getPassWord())) {
​
         //认证成功,则发放令牌
            Map<String,Object> param = new HashMap<>();
            param.put("user", us);
            String jwt = JwtUtils.createJwt(param, JwtUtils.JWT_WEB_TTL);
            response.addHeader("jwt", jwt);
​
​
            re = new RetrunData(1, "登录成功", us);
        } else {
            re = new RetrunData(-1, "登录失败", "ns");
        }
​
        return re;
    }

 在用户登陆控制器进行认证,成功,则发放令牌

Map<String,Object> param = new HashMap<>();

param.put("user", us);//保存用户信息

//获取jwt头 如果认证成功 则发放令牌 并刷新令牌 保存到用户信息中 下次登陆就跨域获取用户信息中刷新后的令牌

String jwt = JwtUtils.createJwt(param, JwtUtils.JWT_WEB_TTL);

response.addHeader("jwt", jwt);

  • ​5)测试后台的验证服务,确定在验证通过的前提下可以正确的生成jwt令牌。
     

3. 前台加入jwt功能

1) 在store/state.js中加入jwt变量

//存放全局参数的容器,组件可以通过state.js获取全局参数
const state = {
  LeftAsideState: 'open',
  PersonName:'张飞',
  
  //存放jwt令牌
  jwt:null
}

export default state

2)在store/mutations.js中加入设置jwt方法 

//设置JWT令牌
  setJwtToken: (state, payload) => {
    state.jwt = payload.jwt;
  }

3)在store/getters.js中加入获取jwt的方法 

 //获取jwt令牌
  getJwtToken: function(state) {
    return state.jwt;
  }

4)修改main.js应用入口 

//将vue实例赋给window.vm属性,以便于在http.js的响应拦截器和请求拦截器中获取vue实例
window.vm = new Vue({
  el: '#app',
  router,
  store,
  components: {App},
  template: '<App/>'
})


5)完善api/http.js中的请求拦截器和响应拦截器

// 请求拦截器
axios.interceptors.request.use(function(config) {
  //获取store中的jwt令牌,如果正常获取,将其放入请求头,
  //在调用后台服务时,服务会通过过滤器验证jwt令牌的有效性
  //如果没有令牌或令牌无效则拒绝服务。
  let jwt = window.vm.$store.getters.getJwtToken;
  if(jwt) {
    config.headers['jwt'] = jwt;
  }
  return config;
}, function(error) {
  return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(function(response) {
  //获取响应头中的jwt令牌数据(认证服务将生成的令牌放入header的jwt属性中)
  //如果获取到jwt令牌,在保存到store中,以后发送请求时需要将令牌放入请求头
  let jwt = response.headers['jwt'];
  if(jwt) {
      window.vm.$store.commit('setJwtToken',{jwt:jwt});
  }
  return response;
}, function(error) {
  return Promise.reject(error);
});
 

配置完就可以测试了,最后结果:

 

 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值