2021年1月23日 springboot + jwt + axios + vue-element ui 简单demo

简介

demo在这里
https://github.com/tao1993/spring-boot-demos/tree/main/demo4

jwt的机制大概是后端生成一个token,前端自己每次请求带上这个token来给后端做认证

JWT流程
-》 用户登录
-》 服务器根据用户信息生成一个token作为令牌
-》令牌保存在客户端(一般是localStorage或sessionStorage),服务端不做保存
-》客户端之后每次请求都要带该令牌 (一般是http的header的authorization)
-》 服务端只需要验证该令牌 (签名是否正确,token是否过期,token的接收方是否是自己 等等)
验证ok就放行,不ok就不放行

三个组成部分就是三个json对象,最后会被整合成一个字符串
头部 header
负载 payload
签名 signature
在这里插入图片描述

参考资料

jwt的部分我是跟着b站的视频教程学的,参考这个,通俗易懂
【编程不良人】JWT认证原理、流程整合springboot实战应用,前后端分离认证的解决方案!

前端部分,vue和element ui 用来做界面,axios用来发请求,都是第一次用,前台代码我乱写的
vue文档
axios中文文档
element ui 的NavMenu 导航菜单
Window localStorage 属性

环境

spring boot 2.3.7
jwt 3.12.0

前端效果

未登录
在这里插入图片描述

登录后
在这里插入图片描述
访问被保护资源
在这里插入图片描述
在这里插入图片描述

目录结构

在这里插入图片描述

集成过程

1.添加依赖

jwt用这个

      <dependency>
          <groupId>com.auth0</groupId>
          <artifactId>java-jwt</artifactId>
          <version>3.12.0</version>
      </dependency>

pom.xml


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.12.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

2.封装JWT

src/main/java/cc/mm/demo4/UtilJWT.java

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.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class UtilJWT {

    //加密用到的私钥
    private static final String SECRET_KEY = "xxxabc!@#$$";

    public static String createToken(Map<String, String> map) {

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, 24 * 7);  //token 设定7天过期
        JWTCreator.Builder builder = JWT.create();
        //通过withClaim()放一些用户信息,这些信息是可以被轻松解码解密的,不要放敏感信息在这里
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        String token = builder
                .withExpiresAt(calendar.getTime())  //设定token 过期信息
                .sign(Algorithm.HMAC384(SECRET_KEY));  //指定加密算法,注意创建token用到的加解密算法要和后面的verify()验证 统一
        return token;

    }

    //返回 ok  表示验证成功
    //返回其他的表示错误信息
    public static String verify(String token) {
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC384(SECRET_KEY)).build();
        try {
            DecodedJWT verify = jwtVerifier.verify(token);
            return "ok";
        } catch (SignatureVerificationException e) {
            return "无效签名";
        } catch (TokenExpiredException e) {
            return "token过期";
        } catch (AlgorithmMismatchException e) {
            return "token算法不一致";
        } catch (Exception e) {
            return "无效签名(其他错误)";
        }
    }

    public static DecodedJWT getDecodedJWT(String token) {
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC384(SECRET_KEY)).build();
        DecodedJWT verify = jwtVerifier.verify(token);
        return verify;
    }

}

3.配置拦截器

src/main/java/cc/mm/demo4/interceptors/JwtInterceptor.java

import cc.mm.demo4.UtilJWT;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        String verifyResult = UtilJWT.verify(token);
        if(verifyResult.equals("ok")) {
            //验证成功  放行
            return true;
        } else {
            //验证失败  返回一个json
            JSONObject json = new JSONObject();
            json.put("result","fail");
            json.put("info",verifyResult);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().println(json);
            return false;
        }

    }

}

src/main/java/cc/mm/demo4/ConfigInterceptor.java

import cc.mm.demo4.interceptors.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class ConfigInterceptor implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                .addPathPatterns("/profile");   //测试demo,仅对 /profile 路径做token验证
                //.excludePathPatterns("/")

    }
}

4.controller和enity

src/main/java/cc/mm/demo4/controller/HomeController.java

import cc.mm.demo4.UtilJWT;
import cc.mm.demo4.enity.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Controller
public class HomeController {


    @GetMapping("/")
    String index(){
        return "index";
    }


    //我的资料 数据,在拦截器里面配置了要带token的请求才能访问
    @PostMapping ("/profile")
    @ResponseBody
    Object profile(HttpServletRequest req){
        String token = req.getHeader("token");
        String strPayload = UtilJWT.getDecodedJWT(token).getPayload();
        strPayload = new String(Base64.getDecoder().decode(strPayload));
        //测试,简单返回一些内容
        JSONObject json = JSON.parseObject(strPayload);
        json.put("info","哈哈哈");
        return json;
    }


    //  接收前台的用户登录请求,验证账号和密码
    //  一开始发现 axios发的请求参数 我spring boot后台接收不到,解决办法参考:
    //   https://blog.csdn.net/qq_36208461/article/details/103079514
    //  我是通过后台加 @RequestBody   解决的,前台axios不做改动,使用默认配置
    @RequestMapping ("/login")
    @ResponseBody
    Object login(@RequestBody User user){
        JSONObject json = new JSONObject();

        //这里因为测试demo,简单模拟验证过程
        if(user.getUserName().equals("abc") && user.getUserPass().equals("123")) {
            //账号密码对的上说明登陆成功,开始用jwt生成一个token返回给前端,让前端自己去把token保存到localStorage
            //可以把一些用户信息放进token,但不要放敏感信息
            Map<String,String> map = new HashMap<>();
            map.put("userId","11122");  //因为测试,简单写死
            map.put("userName",user.getUserName());
            String token = UtilJWT.createToken(map);
            System.out.println(token);
            //给前端返回信息
            json.put("token",token);
            json.put("result","success");
        }else {
            json.put("result","fail");  //登录失败
        }
        return json;
    }

}

src/main/java/cc/mm/demo4/enity/User.java

import lombok.Data;

@Data
public class User {
    String userName;
    String userPass;
}

5.前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <!-- 引入vue  -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 引入element ui  -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <!-- 引入axios  -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
<div id="app">
    <el-menu
            :default-active="activeIndex2"
            class="el-menu-demo"
            mode="horizontal"
            @select="handleSelect"
            background-color="#545c64"
            text-color="#fff"
            active-text-color="#ffd04b">

        <el-menu-item index="1">首页</el-menu-item>


        <!-- <el-menu-item v-if="isLogined"  >-->
        <el-menu-item @click="postProfileWithToken">
            我的资料(带token请求)
        </el-menu-item>

        <el-menu-item @click="postProfileWithoutToken">
            我的资料(不带token请求)
        </el-menu-item>

        <!--登录时显示-->
        <el-menu-item v-if="isLogined" @click="onLogout">注销</el-menu-item>


        <!--未登录时显示-->
        <el-menu-item v-if="!isLogined" index="4">
            <el-form :inline="true" :model="userData" class="demo-form-inline">
                <el-form-item>
                    <el-input style="margin-top: 7px;" v-model="userData.userName" placeholder="账号"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-input style="margin-top: 7px;" v-model="userData.userPass" placeholder="密码"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button style="margin-top: 7px;" type="primary" @click="onSubmit">登录</el-button>
                </el-form-item>
            </el-form>
        </el-menu-item>



        <!--登录时显示-->
        <el-menu-item v-if="isLogined">欢迎用户: {{ currentUserName }}</el-menu-item>
    </el-menu>
</div>

</body>

<script>
    //Base64工具,可以用来前台解码token,拿到token的payload的用户信息
    Base64 = { _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode: function(e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = Base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t }, decode: function(e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9+/=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = Base64._utf8_decode(t); return t }, _utf8_encode: function(e) { e = e.replace(/rn/g, "n"); var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t }, _utf8_decode: function(e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t } }
</script>

<script>

    var app = new Vue({
        el: '#app',
        data: {
            activeIndex: '1',
            activeIndex2: '1',
            userData: {
                userName: '',
                userPass: ''
            }
        },
        methods: {
            handleSelect(key, keyPath) {
                //console.log(key, keyPath);
            },
            onSubmit() {

                //用户登录,post请求向后台 验证账号和密码
                let user = this.userData;
                axios.post('/login', {
                    userName: user.userName,
                    userPass: user.userPass
                }).then(function (response) {
                    //console.log(response);

                    if (response.data.result === 'fail') {
                        app.$message('登录失败');
                    } else if (response.data.result === 'success') {
                        console.log(response.data.token)
                        //登录成功,拿到token,放入localStorage
                        localStorage.setItem("token", response.data.token)
                        //刷新页面
                        location.reload();
                    }

                }).catch(function (error) {
                    console.log(error);
                });
            },
            onLogout() {  //注销操作
                //清除localStorage里面的token,然后刷新页面
                localStorage.removeItem("token");
                location.href = "/";
            },
            postProfileWithToken(){  //带上token访问 /profile
                axios({
                    url:'/profile',
                    method:'post',
                    headers: {'token': localStorage.getItem("token")},
                    data:{},
                }).then(function (response) {
                    app.$message(JSON.stringify(response.data));
                    console.log(response);
                }).catch(function (error) {
                    console.log(error);
                });
            },
            postProfileWithoutToken(){  // 不带token访问 /profile
                axios({
                    url:'/profile',
                    method:'post',
                    data:{},
                }).then(function (response) {
                    app.$message(JSON.stringify(response.data));
                    console.log(response);
                }).catch(function (error) {
                    console.log(error);
                });
            }
        },
        computed: {
            isLogined() {
                //判断当前是否有用户已登录状态,从localstorage里面找token
                //没有token说明未登录
                if (localStorage.getItem("token")) {
                    return true;
                }
                return false;
            },
            currentUserName(){
                //解析localStorage里面的token,获得payload部分的用户信息userName
                let token = localStorage.getItem("token");
                let user = decodeURIComponent(escape(window.atob(token.split('.')[1])));
                let userName = JSON.parse(user).userName;
                return userName;
            }
        }

    })
</script>


</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue是一种用于构建用户界面的前端框架,它基于JavaScript和HTML,并且非常适合用于单页面应用程序的开发。它具有轻量级、易学易用的特点,提供了很多现成的组件和工具,可以帮助我们快速构建出漂亮、高效的用户界面。 SpringBoot是一个用于快速开发Java应用程序的框架,它采用了约定优于配置的理念,可以使开发人员更加关注业务逻辑的实现,而不是繁琐的配置。它提供了很多自动化的特性,例如自动配置、自动装配等,可以大大提高开发效率。 Spring Security是一个用于安全认证和授权的框架,它提供了很多安全相关的特性,例如用户认证、访问控制、密码加密等,可以帮助我们构建安全可靠的应用程序。 JWT(JSON Web Token)是一种用于在网络中传输信息的安全标准,它可以实现无状态、可扩展的用户认证机制。通过使用JWT,我们可以在前后端分离的应用中实现可靠的用户认证和授权,而无需在服务端存储会话信息。 Element-UI是一套基于Vue.js的组件库,它提供了大量的美观且易用的UI组件,可以帮助我们快速构建出漂亮的用户界面。 MyBatis Plus是基于MyBatis的增强工具包,它提供了很多强大的特性来简化数据库操作,例如代码生成、分页查询、通用CRUD等。使用MyBatis Plus,我们可以更加方便地进行数据库操作,提高开发效率。 Vue-Element-Admin是一个基于VueElement-UI的后台管理系统模板,它提供了丰富的组件和布局,可以帮助我们快速构建出美观、易用的后台管理系统。同时,它还集成了一些常用的功能,例如用户管理、权限控制、数据展示等,可以帮助我们轻松搭建出功能完善的后台管理系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值