Vue+elementui+JWT+AOP+回车键+token自动续期 实现登录功能-不完美地方就是每个方法都要加注解
- 经验分享
- 时间:2023-07-03 19:46
- 1715人已阅读
简介
功能介绍:登录成功后,用户页面1分钟无操作返回登录页面,包括新增等按钮效果:一、导包
🔔🔔好消息!好消息!🔔🔔
如果您需要注册ChatGPT,想要升级ChatGPT4。凯哥可以代注册ChatGPT账号,代升级ChatGPT4
有需要的朋友👉:微信号
功能介绍:登录成功后,用户页面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>
二、
#用户 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
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; } }
//除了登录方法和上传图片方法,其他请求方法上都加自定义注解,
//注=登录方法不要加,不然登录不是直接拦截
<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>
// 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/>' })
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 } ] })
上一篇: 【系统安全】未实施加密
下一篇: 程序员必知必会的基础术语