前后端整合token的身份验证(springboot+vue+jwt)

Token的身份验证

环境

SpringBoot+JWT+Vue+Axious+Vuex实现前后端分离下的身份登陆权限验证

整体思路

  1. 客户端通过用户名和密码向服务器发送请求登陆
  2. 服务器收到请求数据,在数据库进行查询验证
  3. 如果验证成功,服务器签发一个Token给客户端
  4. 客户端可以将Token存放到LocalStorage或者Cookie里
  5. 客服端设置监听,每次跳转路由,就判断 LocalStroage 中有无 Token ,没有就跳转到登录页面,有则跳转到对应路由页面
  6. 在Axios每次调后端接口,都要在请求头中加Token
  7. 在后端设置拦截器,用户登录后的每次请求都会经过这个拦截器校验Token是否有效
  8. 如果验证成功,则继续执行请求,返回请求到的数据

Json Web Token(JWT)

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

  • 简洁(Compact)

    可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

  • 自包含(Self-contained)

    负载中包含了所有用户所需要的信息,避免了多次查询数据库

JWT分为三个部分,分别是

  • header(头部)

  • payload(数据)

  • signature(签名)

前后端分离思路

​ 服务器签发token后,将其传给前端,由于cookie不允许跨域,所以,在前端存入localStorage,再次向后端请求时,读取localStorage里面的token的,将其放入请求头里header里,后端配置拦截器,检验。

​ localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。

##实现过程

后端部分

  • 准备工作

    首先引入依赖

<!--jwt-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.3.0</version>
</dependency>
  • Token生成过程

    设置一个token令牌生成的函数

public class JwtUtils {
//    过期时间设置为15分钟
    private static final long EXPTRE_TIME= 15 * 60 * 1000;
    /**
     * token私钥
     */
    private static final String TOKEN_SECRET="8ae0d24822ef59d9e75745449b3501bc";
    /**
     * 生成签名
     */
    public static String Sign(String userName,String userID){
        try {
            //过期时间
            Date date=new Date(System.currentTimeMillis()+EXPTRE_TIME);
            //私钥加密算法
            Algorithm algorithm=Algorithm.HMAC256(TOKEN_SECRET);
            //设置头部信息
            Map<String,Object>header=new HashMap<>(2);
            header.put("typ","JWT");
            header.put("alg","HS256");
            //附带加密的信息
            return JWT.create()
                    .withHeader(header)
                    .withClaim("loginName",userName)
                    .withClaim("userID",userID)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * token解码,验证权限
     */
    public static boolean verfy(String token){
        try{
            Algorithm algorithm=Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier=JWT.require(algorithm).build();
            DecodedJWT jwt=verifier.verify(token);
            return true;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * token提取内容
     */
    public static Admin getAdmin(String token){
        try{
            Algorithm algorithm=Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier=JWT.require(algorithm).build();
            DecodedJWT jwt=verifier.verify(token);
            Admin admin=new Admin();
            admin.setName(jwt.getClaim("loginName").toString());
            admin.setId(Integer.parseInt(jwt.getClaim("userID").toString()));
            return admin;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }


}
  • Serivice层

​ 前端向后端传入name, psd,验证登录信息是否正确,如果正确则进行生成Token令牌,这里注意的一点是,提取该用户id,在JWT中,不应该在负载里面加入任何敏感的数据。像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了,所以我们这里将id和name进行生成Token,一样可以用来验证。

@Service("adminService")
public class AdminServiceImpl implements AdminService {
    @Resource
    private AdminMapper adminMapper;

    @Override
    public String adminLogin(String name,String psd) {
        try {
            Admin admin = new Admin();
            if (StringUtil.isNotEmpty(name) && StringUtil.isNotEmpty(psd)) {
                admin.setName(name);
                admin.setPsd(psd);
            }
            //2.查询结果为空,则直接返回null
            if (adminMapper.selectOne(admin) == null) {
                return null;
            }
            //数据库匹配,查询该id
            int id=admin.getId();
            //查询成功,则生成token
            String token = JwtUtils.Sign(name, id);
            return token;


        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • controller层

    这里是前后端交互,接收前端传来的信息

 @Autowired
    private AdminService adminService;
    @PostMapping("/login")
    public ResponseEntity<String> adminLogin(@RequestParam("username")String userName,@RequestParam("password") String password,HttpServletRequest request,
                                           HttpServletResponse response){
        String token=this.adminService.adminLogin(userName,password);
     // System.out.print(token);
        if (StringUtils.isBlank(token)){
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        return ResponseEntity.ok("Bearer:" + token);
    }
  • 拦截器

​ 这里我们是重写preHandle方法,即我们前端发送请求时,在controller没有接收之前,我们进行拦截请求信息,因为前端是将Token放在请求头里的,我们只需要获取到请求头的信息,然后调用前面JwtUtils类进行验证

public class JwtInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
        String authHeader=request.getHeader("Authorization");
        if(authHeader==null||!authHeader.startsWith("Bearer:")){
            return false;
        }
        //获得token
        String token = authHeader.substring(7);
        //验证token

        return JwtUtils.verfy(token);
    }
  • 拦截器配置

​ 进行注册配置,Springboot的配置拦截器方式,这里可以放掉一些不需要校验token的路由

@Configuration
public class GlobalConfig implements WebMvcConfigurer {

        @Override
         public void addInterceptors(InterceptorRegistry registry) {
            //添加拦截器
            registry.addInterceptor(new JwtInterceptor()).excludePathPatterns("/admin/login","/admin/verify");//放掉某些特定不需要校验token的路由
        }

}

前端部分

说明

​ 这里我是用Vue-cli脚手架进行搭建,使用的是Vuetify框架

  • 准备工作

安装Vuex ,这里使用的nmp安装,在项目控制台输入下面命令

npm install vuex --save
  • 创建store,并在里面新建index.js

在这里插入图片描述

src/store/index.js代码:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const  store=new  Vuex.Store({
  state:{
    //赋值
    token:localStorage.getItem('token') ? localStorage.getItem('token'):''
  },
  mutations:{
    setToken(state,token){
      state.token=token;
    //   这段代码token.token , 是因为在login.vue中调用这个放法传进来的是一个对象
    // (即使你觉的你传进来的是一个字符串,不知道为什么会被放到object里去),
    //   传进来的对象里有token这个属性
      localStorage.setItem("token",token.token);
    } ,
    delToken(state){
      state.token='';
      localStorage.removeItem("token");
    }
  }
})
export default store;

​ 这里我是封装了axious,在这里面将每个axious的请求把Token添加到请求头,同时,后面使用axious时,可以通过this.$https调用,当然这里需要在main.js里进行注册配置

​ http.js代码

import Vue from 'vue'
import axios from 'axios'
import config from './config'
import router from "./router";
import store from "./store";

axios.defaults.baseURL = config.api; // 设置axios的基础请求路径
axios.defaults.timeout = 2000; // 设置axios的请求时间

// axios.interceptors.request.use(function (config) {
//   // console.log(config);
//   return config;
// })

axios.loadData = async function (url) {
  const resp = await axios.get(url);
  return resp.data;
};
// 添加请求拦截器
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
//判断是否存在token,如果存在将每个页面header都添加token
  if(store.state.token){
    config.headers.common['Authorization']=store.state.token.token
  }

  return config;
}, error => {
// 对请求错误做些什么
  return Promise.reject(error);
});

// http response 拦截器
axios.interceptors.response.use(
  response => {

    return response;
  },
  error => {

    if (error.response) {
      switch (error.response.status) {
        case 401:
          this.$store.commit('del_token');
          router.replace({
            path: '/login',
            query: {redirect: router.currentRoute.fullPath}//登录成功后跳入浏览的当前页面
          })
      }
    }
    return Promise.reject(error.response.data)
  });


Vue.prototype.$http = axios;// 将axios添加到 Vue的原型,这样一切vue实例都可以使用该对象

然后在main.js进行注册

在这里插入图片描述

​ store是将store挂载到Vue上,后面可以用this.$store 来获取store,同理router也是。

​ 在router文件里,进行导航守卫

// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    next();
  } else {
    let token = localStorage.getItem('token');
    if (token === null || token === '') {
      next('/login');
    } else {
       axios.get("/admin/verify",{
        params:{
          token:token
        }
      }).then(resp=>{
        console.log(resp.data)
         if(resp.data){
           console.log("没拦截")
           next();
         }else {
           console.log("被拦截")
           next('/login');
         }
      })

    }
  }
}

亲测可用,
本人vx:sun632928843,有需要的 可以加下 一起交流学习

  • 8
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值