目录
流程简介
借助hutool工具绘制二维码,用token存储验证码及过期时间,后端在响应头中放入token。前端将token存入vuex中,登陆时拿取。
效果图
依赖
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
后端代码
后端主要分为两个流程:验证码生成,验证码校验。生成验证码借助Hutool工具,使用Jwt工具类加密存储、校验
验证码生成
controller
@RequestMapping(value ="/verify")
public void getVerify(HttpServletResponse response, HttpServletRequest request){
response.setContentType("image/jpeg");
//不缓存
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expire",0);
RandomValidateCode code = new RandomValidateCode();
try{
code.creatRandCode(request,response);
log.info("验证码生成成功");
//下面方法是自己用java工具类绘制的验证码
// code.getRandcode(request,response);
}catch (Exception e){
log.error("验证码生成失败");
}
}
绘制验证码
public void creatRandCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
//hutool工具类 宽度 高度 位数 线条数
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 15);
lineCaptcha.write(response.getOutputStream());
Long currentTimeMillis = System.currentTimeMillis();
String signRandom = JwtUtils.signRandom(lineCaptcha.getCode(), currentTimeMillis);
response.setHeader("CODEKEY", signRandom);
response.setHeader("Access-Control-Expose-Headers", "CODEKEY");
}
jwtUtils
@ConfigurationProperties(prefix = "Demo.jwt")
使用注解读取配置(验证码过期时间这里直接写到代码中了)
private String secret;
private long expire;
private String header;
public static final long RANDOM_TIME = 60*1000;
/**
* 放入验证码以及过期时间
* 加密盐为 YunMengQingYanLou##Demo
* @param code
* @param currentTime
* @return
*/
public static String signRandom(String code,Long currentTime){
String token = null;
Date expireDate = new Date(currentTime+RANDOM_TIME);
token = Jwts.builder()
.claim("code",code)
.claim("currentTime",currentTime)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, "YunMengQingYanLou##Demo")
.compact();
return token;
}
验证码校验
只展示验证码相关逻辑
@Override
public SysResult login(LoginParam loginParam, HttpServletResponse response, HttpServletRequest request) throws IllegalAccessException {
/**检查参数是否合法
* 使用jwt生产token返回前端
* token放入redis中 redis token user 设置过期时间
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
String verifyCode = loginParam.getVerifyCode();
//获取验证码
String codekey = request.getHeader("Codekey");
try {
Claims claim = JwtUtils.getClaim(codekey);
String code = claim.get("code").toString();
Long currentTime = (Long) JwtUtils.getClaim(codekey).get("currentTime");
Long randomTime = currentTime + (Long) JwtUtils.RANDOM_TIME;
log.info("验证码时间" + randomTime);
long nowTime = System.currentTimeMillis();
if (randomTime < nowTime) {
return SysResult.fail("验证码已失效");
}
if (!verifyCode.equals(code)) {
return SysResult.fail("验证码不正确");
}
} catch (Exception e) {
log.error(e.getMessage());
throw new IllegalAccessException("系统异常");
}
password = DigestUtils.md5DigestAsHex((password + slat).getBytes());
SysUser sysUser = sysuserService.findUser(account);
Assert.notNull(sysUser, "没有该用户");
String pwd = sysUser.getPassword();
if (!password.equals(pwd)) {
return SysResult.fail("密码不正确");
}
String jwt = jwtUtils.generateToken(sysUser.getId());
//将jwt输送Header
response.setHeader("Authorization", jwt);
response.setHeader("Access-control-Expose-Headers", "Authorization");
return SysResult.success(sysUser);
}
JwtUtils获得claim 注意加密盐一致
/**
* 校验 出错返回为空
* @param token
* @return
*/
public static Claims getClaim(String token) {
try {
return Jwts.parser()
.setSigningKey("YunMengQingYanLou##Demo")
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.error("Token解析异常 ", e);
return null;
}
}
后端代码至此全部完毕
前段代码
前段代码逻辑主要是请求后端接口接受验证码信息,放入store中,登录时拿取并放入请求头
<template>
<div class="background">
<div class="log">
<div class="title">YunMeng</div>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="account">
<el-input v-model="ruleForm.account"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="ruleForm.password"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="verifyCode" class="veriftyCode">
<el-input type="verifyCode" style="width: 50%;" v-model="ruleForm.verifyCode"></el-input>
<img :src="verifyurl" alt="单击刷新" ref="verifyImg" class="verifyImg" @click="getVerifty()" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')" style="width: 110px">登录</el-button>
<el-button @click="resetForm('ruleForm')" style="width: 120px">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
获取验证码Js
mounted() {
this.getVerifty()
},
methods: {
getVerifty() {
const _this = this;
_this.$axios.get("/verify", {
responseType: 'blob'
}).then(res => {
if (res) {
const verifycode = res.headers['codekey'];
_this.verifyurl = window.URL.createObjectURL(new Blob([res.data]))
_this.$store.commit("SET_VERIFTY", verifycode);
} else {
this.$message.error("获取验证码失败")
}
})
},
store/index.js
只看verifty相关代码即可
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
verifty:'',
token:'',
userInfo:JSON.parse(sessionStorage.getItem("userInfo")),
menu:JSON.parse(sessionStorage.getItem("menu"))
},
mutations: {
SET_VERIFTY:(state,verifty) =>{
state.verifty = verifty
sessionStorage.setItem("verifty",verifty)
},
SET_MENU:(state,menu) =>{
state.menu = menu
sessionStorage.setItem("menu",JSON.stringify(menu))
},
//数据的改变
SET_TOKEN: (state,token) =>{
state.token = token
localStorage.setItem("token",token)
},
SET_USERINFO: (state,userInfo) =>{
state.userInfo = userInfo
sessionStorage.setItem("userInfo",JSON.stringify(userInfo))
//不能存字符串
},
REMOVE_INFO:(state) =>{
state.token = '';
state.verifty = '';
state.userInfo = {};
state.menu='';
localStorage.setItem("token",'')
sessionStorage.setItem("userInfo", JSON.stringify(''))
sessionStorage.setItem("menu",JSON.stringify(''))
sessionStorage.setItem("verifty",'')
}
},
getters:{
getUser: state =>{
return state.userInfo
},
getToken:state=>{
return state.token
},
getMenu:state => {
return state.menu
},
getVerifty:state => {
return state.verifty
}
},
actions: {
},
modules: {
}
})
登录JS
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
const _this = this;
_this.$axios.post("/login", _this.ruleForm, {
headers: {
"Codekey":sessionStorage.getItem("verifty")
}
}).then(res => {
console.log(res)
if (res.data.status == 200) {
// //存储(共享)全局变量jwt和userInfo
const jwt = res.headers['authorization'];
const userInfo = res.data.data
const admin = userInfo.admin;
const avator = userInfo.avatar;
_this.$store.commit("SET_TOKEN", jwt);
_this.$store.commit("SET_USERINFO", userInfo)
console.log(userInfo)
_this.$router.push({
path: "/home",
query: {
adminId: admin,
pit: avator
}
})
} else {
this.$message.error(res.data.msg)
this.getVerifty();
}
})
} else {
console.log('error submit!!');
return false;
}
});
},
前段完整代码
<template>
<div class="background">
<div class="log">
<div class="title">YunMeng</div>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="account">
<el-input v-model="ruleForm.account"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="ruleForm.password"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="verifyCode" class="veriftyCode">
<el-input type="verifyCode" style="width: 50%;" v-model="ruleForm.verifyCode"></el-input>
<img :src="verifyurl" alt="单击刷新" ref="verifyImg" class="verifyImg" @click="getVerifty()" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')" style="width: 110px">登录</el-button>
<el-button @click="resetForm('ruleForm')" style="width: 120px">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
verifyurl: '',
ruleForm: {
account: '',
password: '',
verifyCode: ''
},
rules: {
account: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
},
{
min: 3,
max: 15,
message: '长度在 3 到 15 个字符',
trigger: 'change'
}
],
password: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
verifyCode: [{
required: true,
message: '请输入验证码',
trigger: 'blur'
}],
}
};
},
mounted() {
this.getVerifty()
},
methods: {
getVerifty() {
const _this = this;
_this.$axios.get("/verify", {
responseType: 'blob'
}).then(res => {
if (res) {
const verifycode = res.headers['codekey'];
_this.verifyurl = window.URL.createObjectURL(new Blob([res.data]))
_this.$store.commit("SET_VERIFTY", verifycode);
} else {
this.$message.error("获取验证码失败")
}
})
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
const _this = this;
_this.$axios.post("/login", _this.ruleForm, {
headers: {
"Codekey":sessionStorage.getItem("verifty")
}
}).then(res => {
console.log(res)
if (res.data.status == 200) {
// //存储(共享)全局变量jwt和userInfo
const jwt = res.headers['authorization'];
const userInfo = res.data.data
const admin = userInfo.admin;
const avator = userInfo.avatar;
_this.$store.commit("SET_TOKEN", jwt);
_this.$store.commit("SET_USERINFO", userInfo)
console.log(userInfo)
_this.$router.push({
path: "/home",
query: {
adminId: admin,
pit: avator
}
})
} else {
this.$message.error(res.data.msg)
this.getVerifty();
}
})
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<style scoped>
.verifyImg {
width: 96px;
height: 40px;
border-radius: 8px;
vertical-align: middle;
margin-left: 12px;
}
.background {
background: url("../assets/35.jpg");
width: 100%;
height: 100%;
position: fixed;
background-size: 100% 100%;
}
.title {
font-size: 70px;
font-family: 黑体;
color: black;
margin-left: 30px;
padding-top: 20px;
text-align: center;
}
.log {
background: rgba(255, 255, 255, 0.6);
box-shadow: 0 5px 30px 0 rgba(0, 0, 0, 0.2);
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 650px;
height: 400px;
border-radius: 20px;
}
.demo-ruleForm {
margin-top: 30px;
margin-left: 40px;
max-width: 500px;
}
</style>