Vue+elementui+JWT+AOP+回车键+token自动续期 实现登录功能-不完美地方就是每个方法都要加注解

  • 作者: 凯哥Java(公众号:凯哥Java)
  • 经验分享
  • 时间:2023-07-03 19:46
  • 1715人已阅读
简介 功能介绍:登录成功后,用户页面1分钟无操作返回登录页面,包括新增等按钮效果:一、导包               

🔔🔔好消息!好消息!🔔🔔

 如果您需要注册ChatGPT,想要升级ChatGPT4。凯哥可以代注册ChatGPT账号代升级ChatGPT4

有需要的朋友👉:微信号 kaigejava2022

功能介绍:登录成功后,用户页面1分钟无操作返回登录页面,包括新增等按钮
效果:


一、导包

<!--   jwt jar 包-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>

<!--   aop jar-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
<!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

二、

application.properties配置文件:

#用户
spring.datasource.username=root
#密码
spring.datasource.password=123123
#连接是数据库
spring.datasource.url=jdbc:mysql://localhost:3306/jin1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#打印Sql
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#扫描.xml文件
mybatis-plus.mapper-locations=classpath*:xml/*.xml


# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端?
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=100
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=PT10S
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=30
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=1
#链接超时时间
spring.redis.timeout=PT10S

三、定义登录的controller:

package com.kaigejava.controller;

import com.kaigejava.common.ResultCode;
import com.kaigejava.common.ResultObj;
import com.kaigejava.entity.User;
import com.kaigejava.service.UserService;
import com.kaigejava.util.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/login")
@CrossOrigin //跨域
public class LoginController {

    @Autowired
    private UserService userService;
     private  static final String ACCESS_TOKEN="ACCESS_TOKEN";

    @Autowired
    private RedisTemplate redisTemplate;

    //登录
    @PostMapping("login")
     public ResultObj login(String username,String password) {
        //判断用户名密码为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            //ResultObj统一返回值
            return ResultObj.error(ResultCode.USERNAME_PASSWORD_ISNULL);
        }

        //判断用户名是否存在
        User user = userService.queryUserByUsername(username);
        if (user == null) {
            return ResultObj.error(ResultCode.USER_NOEXIST);
        }

        //判断密码是否正确
        if (!password.equals(user.getPassword())) {
            return ResultObj.error(ResultCode.PASSWORD_ERROR);
        }

        //登录成功,生成token
        String token = JWTUtil.createToken(user);

		//获取当前时间的毫秒值
        String currentTime = String.valueOf(System.currentTimeMillis());
        //拼接存到redis中的Key中
        String accessKey=ACCESS_TOKEN+user.getId()+":"+token;
        //往拼接存到redis中的Key存value值
        redisTemplate.opsForValue().set(accessKey,currentTime);
        //设置redis key的过期时间 2分钟
        redisTemplate.expire(accessKey,2, TimeUnit.MINUTES);


        return ResultObj.success(token);


    }


}

四、定义同意返回值类对象

package com.kaigejava.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultObj {
    //状态码
    private Integer code;

    //信息
    private  String msg;

    //数据
    private Object data;

    public static ResultObj success(Object data){
        return new ResultObj(ResultCode.SUCCESS.getCode(),ResultCode.SUCCESS.getMsg(),data);
    }


    public  static ResultObj error(ResultCode resultCode){
        return new ResultObj(resultCode.getCode(),resultCode.getMsg(),null);
    }
}


五、定义一个enum类:

package com.kaigejava.common;

public enum ResultCode {
    SUCCESS(200,"操作成功"),
    ERROR(500,"操作失败"),
    USERNAME_PASSWORD_ISNULL(1001,"用户名或密码为空"),
    USER_NOEXIST(1002,"用户不存在"),
    PASSWORD_ERROR(1003,"密码错误"),
    TOKEN_ERROR(1004,"登录失败")
    ;

    private Integer code;
    private  String msg;

    ResultCode(Integer code,String msg){
        this.code=code;
        this.msg=msg;
    }

    public Integer getCode(){
        return code;
    }

    public void setCode(Integer code){
        this.code=code;
    }

    public String getMsg(){
        return msg;
    }

    public void setMsg(String msg){
        this.msg=msg;
    }


}


六、定义一个切面类:

package com.kaigejava.common;

import com.kaigejava.annotation.LoginAnnotation;
import com.kaigejava.util.JWTUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@Component
@Aspect
public class LoginAop {

    @Resource
    private HttpServletRequest request;

	 private  static final String ACCESS_TOKEN="ACCESS_TOKEN";

    @Autowired
    private RedisTemplate redisTemplate;
    
    //扫描controller层所有自定义注解
    //loginAnnotation自定义注解
    @Around(value = "execution(* com.ff.controller.*.*(..)) && @annotation(loginAnnotation)")
    public Object loginAround(ProceedingJoinPoint joinPoint, LoginAnnotation loginAnnotation) {
        Object proceed=null;
       //验证token的值
        String token = request.getHeader("Authorization-token");
        ResultObj resultObj = JWTUtil.verifToken(token);


        //失败的情况
        if(resultObj.getCode()!= 200){
            return resultObj;
        }

		 //获取data中数据
        Claims claims = (Claims)resultObj.getData();
        //拼接存到redis中的Key中
        String accessKey=ACCESS_TOKEN+claims.get("id")+":"+token;

        //验证redis中的token得值是否存在
        if(!redisTemplate.hasKey(accessKey)){
            //登录失败
            return ResultObj.error(ResultCode.TOKEN_ERROR);
        }
        //如果redis中的token得值存在,续签
        //获取当前时间的毫秒值
        String currentTime = String.valueOf(System.currentTimeMillis());
        //往拼接存到redis中的Key存value值
        redisTemplate.opsForValue().set(accessKey,currentTime);
        //设置redis key的过期时间 2分钟
        redisTemplate.expire(accessKey,2, TimeUnit.MINUTES);

        try {
            //执行目标方法
             proceed = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }


        return proceed;
    }



}


七、定义自定义注解

package com.kaigejava.annotation;

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 LoginAnnotation {
}


八、定义自定义注解:JWTUtil工具类:

package com.kaigejava.util;

import com.kaigejava.common.ResultCode;
import com.kaigejava.common.ResultObj;
import com.kaigejava.entity.User;
import io.jsonwebtoken.*;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {

    //生成Toke的方法

    public static String createToken(User user) {
        //token分为3部分
        //第一部分我们称它为头部(header),
        // 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),
        // 第三部分是签证(signature).

        //头部
        Map<String, Object> headerMap = new HashMap<String, Object>();
        //HS256加密方式
        headerMap.put("alg", "HS256");
        headerMap.put("type", "JWT");

        //有效载荷
        Map<String, Object> payloadMap = new HashMap<String, Object>();
        payloadMap.put("username", user.getUsername());
        payloadMap.put("userid", user.getPassword());


        //失效时间   s设置不过期的token
        long timeMillis = System.currentTimeMillis();
        //设置token时间 999999999 毫秒=11.5740740625 天
        long endTime = timeMillis + 999999999 ;

        //签证,签名
        String token = null;
        try {
            token = Jwts.builder()
                    .setHeader(headerMap)
                    .setClaims(payloadMap)
                    .setExpiration(new Date(endTime))
                    .signWith(SignatureAlgorithm.HS256, "rtet")
                    .compact();
            System.out.println(token);
        } catch (ExpiredJwtException e) {
            e.getClaims();
        }
        return token;
    }

    //解密token
    public static ResultObj verifToken(String token) {
        try {
            //通过TWTs的parser 方法 接受
            //根据人名key解密
            Claims claims = Jwts.parser().setSigningKey("rtet")
                    .parseClaimsJws(token)
                    .getBody();
            return ResultObj.success(claims);
        } catch (Exception e) {
          return ResultObj.error(ResultCode.TOKEN_ERROR);
        }

    }
}


九、最后使用俩个类序列化Redis参数,不然乱码

9.1:

package com.kaigejava.cache;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (null == t) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (null == bytes || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }
}

9.2:

package com.kaigejava.cache;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {


    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        //使用fastjson序列化
        com.ff.cache.FastJsonRedisSerializer fastJsonRedisSerializer = new com.ff.cache.FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


}

//除了登录方法和上传图片方法,其他请求方法上都加自定义注解,
//注=登录方法不要加,不然登录不是直接拦截

8d627c3f7d6b838d1e25c0d1a88e6453.png

前端=======================================

Login.Vue页面代码:

<template >
  <div class="login">
    <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm aaa" >
      <el-form-item label="用户名" prop="username">
        <el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="确认密码" prop="password">
        <el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"  @keyup.enter="keyDown"  @click="submitForm('ruleForm')">提交</el-button>
        <el-button @click="resetForm('ruleForm')">重置</el-button>
      </el-form-item>
    </el-form>


  </div>

</template>

<script>
    export default {
        name: "Login",
      data(){
        return {
          ruleForm: {
            password: '',
            username: ''
          },
          rules: {
            password: [
              { required: true, message: '请输入密码', trigger: 'blur' },
              { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
            ],
            username: [
              { required: true, message: '请输入用户名称', trigger: 'blur' },
              { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
            ]
          }
        }
      },
      methods: {
        submitForm(formName) {
          this.$refs[formName].validate((valid) => {
            if (valid) {
           var _this=this;
              this.$axios.post("http://localhost:8080/login/login",this.$qs.stringify(this.ruleForm)).then(function (result) {
                if(result.data.code!=200){
                  _this.$message.error(result.data.msg)
                }else{
                  //将token放到localStorage
                  localStorage.setItem("token",result.data.data);
                  //跳转首页路径
                  _this.$router.push("/main");

                }
              })

            } else {
              console.log('error submit!!');
              return false;
            }
          });
        },
        resetForm(formName) {
          this.$refs[formName].resetFields();
        },
        keyDown(e){
          //如果是回车则执行登录方法
          if(e.keyCode == 13){
            //需要执行的方法
            this.submitForm('ruleForm');
          }
        }
      },
      mounted () {
        //绑定事件
        window.addEventListener('keydown',this.keyDown);
      },
      //销毁事件
      destroyed(){
        window.removeEventListener('keydown',this.keyDown,false);
      }
    }
</script>

<style scoped>
  /*设置样式*/
 .demo-ruleForm{
   border: 1px solid #DCDFE6;
   width: 350px;
   margin: 180px auto;
   padding: 35px 120px 15px 25px;
   border-radius: 100px;
   -webkit-border-radius: 5px;
   -moz-border-radius: 5px;
   box-shadow: 0 0 25px #909399;
 }

/* 背景图片*/
  .login{
    background:url("../assets/55.jpg");
    width: 100%;
    height: 100%;
    position:fixed;
    margin-top: -65px;/*上边距*/
    margin-left: -10px;/*左边距*/
    background-size:100% 100%;
    }

  /* 背景图片*/
  .aaa{
    background:url("../assets/4.jpg");
    width: 26%;
    height: 24%;
    position:fixed;
    margin-top: 260px;/*上边距*/
    margin-left:460px;/*左边距*/
    background-size:100% 100%;
  }

</style>


main.js页面:设置拦截和请求

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import  VueRouter from 'vue-router'
import router from './router'


import './router/directives.js';
import ElemrntUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElemrntUI)
Vue.use(VueRouter)
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios,axios)
Vue.config.productionTip = false


import 'element-ui/lib/theme-chalk/index.css';

import QS from 'qs'

Vue.config.productionTip = false

//发起请求axios请求之前给请求的头设置token的值
axios.interceptors.request.use(config=>{
  config.headers.common['Authorization-token']=localStorage.getItem("token");
  return config;
})

//响应时的拦截
axios.interceptors.response.use(response=>{
/*
  登录错误时
*/
  if(response.data.code == 1004){
    //通过路由进行跳转
    router.push("/");
  }
  return response;

})





Vue.prototype.$axios= axios
Vue.prototype.$qs= QS
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

index.js页面:

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Main from '@/components/Main'


Vue.use(Router)

export default new Router({
  routes: [
    {
      //跳转登录页面
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      //跳转首页
      path: '/main',
      name: 'Main',
      component: Main
    }
  ]
})




TopTop