spring-boot+JWT+vue实现身份校验

spring-boot+vue实现前后端分离
基于springboot+vue实现前后端分离后,对其添加身份校验的功能;
实现思路是在用户登录成功后由服务器为其发放一个通行证,只有拥有通行证,才可以访问前端的部分页面,实现权限区分.
而前端只会确认是否有通行证,不会对通行证的真伪进行检验,后端的工作是对请求进行拦截,来判断用户的通行证的有效性.

一 . 后端搭建

  1. 在pom文件中添加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.8.1</version>
    </dependency>
  1. 创建通行证工具类,并提供生成通行证,校验通行证,获得通行证归属的用户的三个方法;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.stream.StreamSupport;
/*提供令牌生成和校验*/
public class JwtUtil {
	//令牌有效时间
    private static final long EXPIREE_TIME = 5 * 60 * 1000;
    
    private static final String SECRECT = "gw";

    //获得令牌
    public static String getToken(String userId) {
        Date date = new Date(System.currentTimeMillis() + EXPIREE_TIME);
        JWTCreator.Builder builder = JWT.create();
        //为令牌赋值:用户
        builder.withAudience(userId);
        //为令牌赋值:失效时间
        builder.withExpiresAt(date);
        //为令牌加密
        Algorithm algorithm = Algorithm.HMAC256(SECRECT);
        String sign = builder.sign(algorithm);
        //为令牌赋值:用户
        return sign;
    }

	//获得令牌的归属用户
    public static String getUserId(String token) {
        String userId = JWT.decode(token).getAudience().get(0);
        return userId;
    }

//校验通行证是否正确
    public static boolean checkToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRECT);
            JWTVerifier jwtVerifier = JWT.require(algorithm).build();
            DecodedJWT verify = jwtVerifier.verify(token);
            return true;
        } catch (JWTVerificationException ex) {
            throw new RuntimeException("token 失效了");
        }

    }
}
  1. 创建一个自定义注解,用来拦截需要校验的请求,注意加此注解的方法后,需要通行证有效慈爱可以访问.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义一个需要在方法前标识的注解(此方法需要拦截)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtToken {
}
  1. 创建一个处理拦截的类,用来拦截请求

import com.qf.j2005.anno.JwtToken;
import com.qf.j2005.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * 创建对令牌的的拦截工具类
 */
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
    //    执行控制器方法前激发
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        StringBuffer requestURL = request.getRequestURL();
//        获取令牌
        String token = request.getHeader("LINGPAI");
        System.out.println("url:" + requestURL);
        System.out.println("token:" + token);

//        如果执行的不是处理器方法,则不必拦截
        if (!(handler instanceof HandlerMethod)) {
            System.out.println("没加注解不拦截...");
            return true;
        }
        HandlerMethod handler1 = (HandlerMethod) handler;
//        获取拦截的方法对象
        Method method = handler1.getMethod();
//        从方法对象中查看是否含有指定的注解
        JwtToken jwtToken = method.getAnnotation(JwtToken.class);
        if (jwtToken != null) {//方法前含有指定注解
//        校验令牌有效性
            boolean b = JwtUtil.checkToken(token);
            if (b) {
                return true;
            } else {
                throw new RuntimeException("token 失效了 请重新登录");
            }
        }
        return true;
    }

    //执行完控制器方法的业务逻辑后触发
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    //执行完控制器逻辑视图后,在进入视图解析器前出发
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

  1. 自定义类InterConfig 对不需要拦截的方法进行配置
package com.qf.j2005;

import com.qf.j2005.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

public class InterConfig implements WebMvcConfigurer {
    //通用来处理各种跨域的问题
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//配置不拦截的路径
        List<String> excludePath = new ArrayList<>();
        excludePath.add("/user_register"); //注册
        excludePath.add("/dealLogin"); //登录
        excludePath.add("/logout"); //登出
        excludePath.add("/static/**");  //静态资源
        excludePath.add("/assets/**");  //静态资源
        registry.addInterceptor(jwtAuthenIntercetpor())
                .addPathPatterns("/**")
//                排除拦截路径
                .excludePathPatterns(excludePath);
    }

    @Bean
    public JwtInterceptor jwtAuthenIntercetpor() {
        return new JwtInterceptor();
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
//                允许跨域的路径
                .addMapping("/**")
//                允许跨域的path
                .allowedOrigins("*")
//                允许凭证
                .allowCredentials(true)
//                允许通过方法
                .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
//               跨域的生存期
                .maxAge(3600 * 24);
    }

}
  1. 在登录的controller中发放令牌

import com.qf.j2005.anno.JwtToken;
import com.qf.j2005.po.Admin;
import com.qf.j2005.service.AdminService;
import com.qf.j2005.utils.JwtUtil;
import com.qf.j2005.utils.Msg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import vo.AdminVo;
import vo.ToKenVo;

import java.util.List;
import java.util.UUID;

@RestController
@Slf4j
public class AdminController {

    @Autowired
    private AdminService adminService;

    @CrossOrigin
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Msg login(@RequestBody AdminVo adminVo) {
        Admin admin = adminService.findAdminByName(adminVo.getUsername());
        if (admin != null) {
            System.out.println(admin + "---" + adminVo);
            if (admin.getPassword().equals(adminVo.getPassword())) {
                admin.setPassword("");
                //密码匹配,登录成功
//                发放令牌
                String userId = UUID.randomUUID().toString();
                String token = JwtUtil.getToken(userId);
//                封装含令牌的对象
                ToKenVo tokentVo = new ToKenVo(admin, token);
                System.out.println("tokentVo" + tokentVo);
                return new Msg(200, "登录成功", tokentVo);
            } else {
                return new Msg(300, "用户名或密码错误", "");
            }
        }
        return new Msg(400, "用户不存在", "");
    }
    }

    @CrossOrigin
    @JwtToken
    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public Msg users() {
        List<Admin> list = adminService.findAll();
        if (list != null) {
            return new Msg(200, "查到了    ", list);
        }
        return new Msg(300, "没找到    ", "");
    }
}

  1. 对查询的请求设置注解,判断通行证的有效性,只有有效的通行证才可以访问此方法

二 . 前端搭建

一. 实现思路

思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码

2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token

3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面

4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面

5、每次调后端接口,都要在请求头中加token

6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401

7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面

二. 实现步骤

  1. npm install vuex --save(安装 Vuex,用于管理状态)
  2. 在index.js配置vuex状态管理
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
    state: {
        // 存储token
        LINGPAI: localStorage.getItem('LINGPAI') ? localStorage.getItem('LINGPAI') : ''
    },
    mutations: {
        // 修改token,并将token 存入localStorage中
        changeLogin(state, user) {
            state.LINGPAI = user.LINGPAI;
            localStorage.setItem('LINGPAI', user.LINGPAI);
        }
    }
});
  1. 在index.js配置路由守卫

Vue.use(Router);
const routes = [{
        path: '/',
        component: login
    },
    {
        path: '/admin',
        component: admin,
        children: [
            { path: 'users', component: users },
            { path: 'orders', component: orders },
            { path: 'hello', component: HelloWorld },
        ]
    }, {
        path: '/orders',
        component: orders
    }, {
        path: '/hello',
        component: HelloWorld
    }
];
const router = new Router({
    mode: 'history',
    routes
});

// 路由守卫
router.beforeEach((to, from, next) => {
    if (to.path == '/' || to.path == '/hello') {
        next();
    } else {
        // 获取当前用户的令牌
        let token = localStorage.getItem('LINGPAI');
        if (token === null || token === '') {
            alert("登录已过期,请前往登录!")
            next('/');
        } else {
            next();
        }
    }
});
export default router;
  1. 在main.js中添加请求拦截
// 引用axios,并设置基础URL为后端服务api地址
var axios = require('axios');
axios.defaults.baseURL = 'http://localhost:8085' //此处是https协议如果不是改成http
    // 将API方法绑定到全局
Vue.prototype.$axios = axios

//给axios添加请求拦截器
axios.interceptors.request.use(
    //  function (config) {
    config => {
        // Do something before request is sent
        if (localStorage.getItem('LINGPAI')) {
            // 如果令牌存在 将令牌添加到请求头部中 key值为LINGPAI
            config.headers.LINGPAI = localStorage.getItem('LINGPAI');
        }
        return config;
    },
    error => {
        // Do something with request error
        return Promise.reject(error);
    });

  1. 登录成功后保存通行证
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$axios
            .post('login', {
              username: this.ruleForm.username,
              password: this.ruleForm.password,
            })
            .then((res) => {
              //  console.log(res);
              if (res.data.code == 200) {
                let token = res.data.info.token
                // 将返回的用户信息和令牌写入localStorage
                localStorage.setItem('LINGPAI', token)
                // 记录用户名
                localStorage.setItem('username', this.ruleForm.username)
                this.$message('欢迎您!' + this.ruleForm.username)
                
                this.$router.push('/admin')
              } else {
                alert(res.data.code + ' ' + res.data.msg)
              }
              //
            })
            .catch((error) => {
              console.log(error)
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },

三级目录

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值