zoukankan      html  css  js  c++  java
  • 【笔记】vue+springboot前后端分离实现token登录验证和状态保存的简单实现方案

    简单实现

    token可用于登录验证和权限管理。
    大致步骤分为:

    1. 前端登录,post用户名和密码到后端。
    2. 后端验证用户名和密码,若通过,生成一个token返回给前端。
    3. 前端拿到token用vuex和localStorage管理,登录成功进入首页。
    4. 之后前端每一次权限操作如跳转路由,都需要判断是否存在token,若不存在,跳转至登录页。
    5. 前端之后的每一个对后端的请求都要在请求头上带上token,后端查看请求头是否有token,拿到token检查是否过期,返回对应状态给前端。
    6. 若token已过期,清除token信息,跳转至登录页。
      具体代码如下:

    前端

    • 登录页
    <template>
        <div class="signin-form">
            <h3 class="sign-title">ticket-sys 登录</h3>
            <div>
                <el-form :model="loginForm" :rules="rules" status-icon ref="ruleForm" class="demo-ruleForm">
                    <el-form-item prop="username">
                        <el-input
                                v-model="loginForm.username"
                                autocomplete="off"
                                placeholder="用户名"
                                prefix-icon="el-icon-user-solid"
                        ></el-input>
                    </el-form-item>
                    <el-form-item prop="password">
                        <el-input
                                type="password"
                                v-model="loginForm.password"
                                autocomplete="off"
                                placeholder="请输入密码"
                                prefix-icon="el-icon-lock"
                        ></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" @click="submitForm" id="login-btn">登录</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </div>
    </template>
    <script>
        import api from '../constant/api';
        import {mapMutations} from "vuex";
        export default {
            name: 'login',
            data() {
                return {
                    loginForm:{
                        username:'',
                        password:''
                    },
                    userToken:'',
                    rules:{
                        username:[
                            { required: true, message: '请输入用户名', trigger: 'blur' },
                        ],
                        password:[
                            { required: true, message: '请输入密码', trigger: 'blur' },
                        ]
                    }
                }
            },
            methods: {
                ...mapMutations(['changeLogin']),
                submitForm() {
                    let v=this;
                    this.$axios({
                        method: 'post',
                        url: api.base_url+'/user/login',
                        data:{
                            'username':v.loginForm.username,
                            'password':v.loginForm.password
                        }
                    }).then(function(res){
                        console.log(res.data);
                        v.userToken = 'Bearer ' + res.data.token;
                        // 将用户token保存到vuex中
                        v.changeLogin({ Authorization:v.userToken });
                        v.$router.push('/home');
                        v.$message('登录成功');
                    }).catch(function(err){
                        console.log("err",err);
                        v.$message('密码或用户名错误');
                    })
                }
            }
        }
    </script>
    <style scoped>...</style>
    
    • vuex状态管理
      /store/index.js
    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    const store = new Vuex.Store({
        state: {
            // 存储token
            Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
        },
        mutations: {
            // 修改token,并将token存入localStorage
            changeLogin (state, user) {
                state.Authorization = user.Authorization;
                localStorage.setItem('Authorization', user.Authorization);
            }
        }
    });
    
    export default store;
    
    • 路由守卫
      /router/index.js
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/home',
        name: 'home',
        component: Home,
      }
      ,
      {
        path: '/about',
        name: 'about',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: function () {
          return import(/* webpackChunkName: "about" */ '../views/About.vue')
        }
      },
      {
        path:'/login',
        name:'login',
        component:function () {
          return import('../views/Login.vue');
        }
      }
    ]
    
    const router = new VueRouter({
     <template>
      <div class="home">
        <el-container>
    <!--      <Header/>-->
    <!--      <el-main>首页</el-main>-->
          <el-button @click="exit">退出登录</el-button>
          <el-button @click="test">携带token的测试请求</el-button>
        </el-container>
      </div>
    </template>
    
    <script>
    // @ is an alias to /src
    import HelloWorld from '@/components/HelloWorld.vue'
    import Header from "../components/Header";
    import api from "../constant/api";
    
    export default {
      name: 'home',
      components: {
        Header,
        HelloWorld
      },
      methods:{
        exit(){
          localStorage.removeItem('Authorization');
          this.$router.push('/login');
        },
        test(){
          this.$axios({
            method: 'get',
            url: api.base_url+'/user/test',
          }).then(function(res){
            console.log("res",res);
          }).catch(function(err){
            console.log("err",err);
          })
        }
      }
    }
    </script>
    <style>...</style>
     mode: 'history',
      base: process.env.BASE_URL,
      routes
    });
    // 导航守卫
    // 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
    router.beforeEach((to, from, next) => {
      if (to.path === '/login') {
        next();
      } else {
        let token = localStorage.getItem('Authorization');
        if (token === 'null' || token === '') {
          next('/login');
        } else {
          next();
        }
      }
    });
    export default router
    
    • 主文件中注册并添加拦截器
      /main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import './plugins/element.js'
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    
    import axios from 'axios' ;
    import Vuex from 'vuex' //引入状态管理
    Vue.prototype.$axios= axios ;
    Vue.use(Vuex) ;
    
    Vue.config.productionTip = false
    Vue.use(ElementUI)
    
    //这里要导入store
    import store from "./store";
    
    // 添加请求拦截器,在请求头中加token
    axios.interceptors.request.use(
        config => {
          if (localStorage.getItem('Authorization')) {
            config.headers.Authorization = localStorage.getItem('Authorization');
          }
    
          return config;
        },
        error => {
          return Promise.reject(error);
        });
    
    new Vue({
        //这里要配置store
      router, store:store,
      render: function (h) { return h(App) }
    }).$mount('#app')
    
    • home页面
    <template>
      <div class="home">
        <el-container>
    <!--      <Header/>-->
    <!--      <el-main>首页</el-main>-->
          <el-button @click="exit">退出登录</el-button>
          <el-button @click="test">携带token的测试请求</el-button>
        </el-container>
      </div>
    </template>
    
    <script>
    // @ is an alias to /src
    import HelloWorld from '@/components/HelloWorld.vue'
    import Header from "../components/Header";
    import api from "../constant/api";
    
    export default {
      name: 'home',
      components: {
        Header,
        HelloWorld
      },
      methods:{
        exit(){
          //退出登录,清空token
          localStorage.removeItem('Authorization');
          this.$router.push('/login');
        },
        test(){
          this.$axios({
            method: 'get',
            url: api.base_url+'/user/test',
          }).then(function(res){
            console.log("res",res);
          }).catch(function(err){
            console.log("err",err);
          })
        }
      }
    }
    </script>
    <style>...</style>
    

    后端

    • 登录controller
    package com.zxc.ticketsys.controller;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/user")
    public class UserController{
        @RequestMapping(value = "/login",method = RequestMethod.POST)
        @ResponseBody
        public String login(@RequestHeader Map<String,Object> he,@RequestBody Map<String,Object> para) throws JsonProcessingException {
            System.out.println(he);
            String username=(String)para.get("username");
            String password=(String)para.get("password");
            HashMap<String,Object> hs=new HashMap<>();
            hs.put("token","token"+username+password);
            ObjectMapper objectMapper=new ObjectMapper();
            return objectMapper.writeValueAsString(hs);
        }
    
        @RequestMapping(value = "/test",method = RequestMethod.GET)
        @ResponseBody
        public String test(@RequestHeader Map<String,Object> he) throws JsonProcessingException {
            System.out.println(he);
            HashMap<String,Object> hs=new HashMap<>();
            ObjectMapper objectMapper=new ObjectMapper();
            return objectMapper.writeValueAsString(hs);
        }
    }
    

    测试

    • 登录

      此时后台的请求头:
    {host=localhost:8088, connection=keep-alive, accept=application/json, text/plain, */*, origin=http://localhost:8080, user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36, sec-fetch-site=same-site, sec-fetch-mode=cors, referer=http://localhost:8080/home, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7}
    
    • 登录成功,进入home页面

      后端返回的token:
    • 前端持有token,访问测试接口
      后端收到请求头:
    {host=localhost:8088, connection=keep-alive, accept=application/json, text/plain, */*, origin=http://localhost:8080, authorization=Bearer tokenadminadmin, user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36, sec-fetch-site=same-site, sec-fetch-mode=cors, referer=http://localhost:8080/home, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7}
    

    带有token,可以进行验证

    • 退出登录,清空token

    后台实际应用

    在实际的应用中,一般需要一个生成token的工具类和一个拦截器对请求进行拦截。

    • token生成工具类
      /utils/TokenUtil.java
    package com.zxc.ticketsys.utils;
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import com.zxc.ticketsys.model.User;
    import java.util.Date;
    
    public class TokenUtil {
    
        private static final long EXPIRE_TIME= 10*60*60*1000;
        private static final String TOKEN_SECRET="txdy";  //密钥盐
        
        /**
         * 签名生成
         * @param user
         * @return
         */
        public static String sign(User user){
            String token = null;
            try {
                Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
                token = JWT.create()
                        .withIssuer("auth0")
                        .withClaim("username", user.getUsername())
                        .withExpiresAt(expiresAt)
                        // 使用了HMAC256加密算法。
                        .sign(Algorithm.HMAC256(TOKEN_SECRET));
            } catch (Exception e){
                e.printStackTrace();
            }
            return token;
        }
    
        /**
         * 签名验证
         * @param token
         * @return
         */
        public static boolean verify(String token){
            try {
                JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
                DecodedJWT jwt = verifier.verify(token);
                System.out.println("认证通过:");
                System.out.println("username: " + jwt.getClaim("username").asString());
                System.out.println("过期时间:      " + jwt.getExpiresAt());
                return true;
            } catch (Exception e){
                return false;
            }
        }
    }
    
    • 拦截器类
      /interceptor/TokenInterceptor.java
    package com.zxc.ticketsys.interceptor;
    
    import com.alibaba.fastjson.JSONObject;
    import com.zxc.ticketsys.utils.TokenUtil;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Component
    public class TokenInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
            if(request.getMethod().equals("OPTIONS")){
                response.setStatus(HttpServletResponse.SC_OK);
                return true;
            }
            response.setCharacterEncoding("utf-8");
            String token = request.getHeader("token");
            if(token != null){
                boolean result = TokenUtil.verify(token);
                if(result){
                    System.out.println("通过拦截器");
                    return true;
                }
            }
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            try{
                JSONObject json = new JSONObject();
                json.put("msg","token verify fail");
                json.put("code","50000");
                response.getWriter().append(json.toJSONString());
                System.out.println("认证失败,未通过拦截器");
            }catch (Exception e){
                e.printStackTrace();
                response.sendError(500);
                return false;
            }
            return false;
        }
    }
    
    • 配置拦截器
      /config/WebConfiguration.java
      注意最好写在一个配置类里,且WebMvcConfigurationSupport和WebMvcConfigurerAdapter不要同时存在
      这里包括处理跨域的配置,而且全部改为implements WebMvcConfigurer接口
    package com.zxc.ticketsys.config;
    
    import com.zxc.ticketsys.interceptor.TokenInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
    import org.springframework.web.servlet.config.annotation.*;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Executors;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    
    /**
     * 跨域请求支持/token拦截
     * tip:只能写在一个配置类里
     */
    @Configuration
    public class WebConfiguration implements WebMvcConfigurer {
    
        private TokenInterceptor tokenInterceptor;
    
        //构造方法
        public WebConfiguration(TokenInterceptor tokenInterceptor){
            this.tokenInterceptor = tokenInterceptor;
        }
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowCredentials(true)
                    .allowedHeaders("*")
                    .allowedMethods("*")
                    .allowedOrigins("*");
        }
    
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer){
            configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
            configurer.setDefaultTimeout(30000);
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            List<String> excludePath = new ArrayList<>();
            //排除拦截,除了注册登录(此时还没token),其他都拦截
            excludePath.add("/user/register");  //登录
            excludePath.add("/user/login");     //注册
            excludePath.add("/static/**");  //静态资源
            excludePath.add("/assets/**");  //静态资源
    
            registry.addInterceptor(tokenInterceptor)
                    .addPathPatterns("/**")
                    .excludePathPatterns(excludePath);
            WebMvcConfigurer.super.addInterceptors(registry);
        }
    }
    
    • 控制器类
    package com.zxc.ticketsys.controller;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.zxc.ticketsys.model.User;
    import com.zxc.ticketsys.utils.TokenUtil;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/user")
    public class UserController{
        @RequestMapping(value = "/login",method = RequestMethod.POST)
        @ResponseBody
        public String login(@RequestBody Map<String,Object> para) throws JsonProcessingException {
            String username=(String)para.get("username");
            String password=(String)para.get("password");
            String token= TokenUtil.sign(new User(username,password));
            HashMap<String,Object> hs=new HashMap<>();
            hs.put("token",token);
            ObjectMapper objectMapper=new ObjectMapper();
            return objectMapper.writeValueAsString(hs);
        }
    
        @RequestMapping(value = "/test",method = RequestMethod.POST)
        @ResponseBody
        public String test(@RequestBody Map<String,Object> para) throws JsonProcessingException {
            HashMap<String,Object> hs=new HashMap<>();
            hs.put("data","data");
            ObjectMapper objectMapper=new ObjectMapper();
            return objectMapper.writeValueAsString(hs);
        }
    }
    
    • 测试
    1. 登录
      成功登录,获得token。
    2. 不带token访问test接口
      被拦截,访问失败。
    3. 带有效token访问test接口
      访问成功,获得数据。

    完整项目代码

    github

  • 相关阅读:
    Odoo开发教程15-管理 Odoo 数据库
    星辉信息科技Odoo开发教程14-源码安装 Odoo第三讲
    星辉信息科技Odoo开发教程13-源码安装 Odoo第二讲
    Odoo开发教程12-源码安装 Odoo
    星辉信息科技Odoo开发教程11-使用 Windows 子系统安装 Linux
    Metasploit 进阶
    Metasploit 一些重要模块使用介绍
    Metasploit 使用基础
    【译】Attacking XML with XML External Entity Injection (XXE)
    CentOS6 安装Sendmail + Dovecot + Roundcubemail
  • 原文地址:https://www.cnblogs.com/zxcoder/p/11964433.html
Copyright © 2011-2022 走看看