首先在登陆的时候自动将用户信息作为载荷,密码作为密钥生成token并传给前端
//生成token的类
public class Token {
public static String getToken(String password){
return JWT.create().withAudience("aa","bb","cc") //将这三个数据放进去作为载荷进行加密
.withExpiresAt(DateUtil.offsetHour(new Date(),2)) //传入当前时间对象,并指定两小时后token过期
//也可以指定时分秒天,比如offsetSecond就表示指定有效期为秒
.sign(Algorithm.HMAC256(password)); //使用用户密码作为token的密钥
}
}
//登陆
public Result login(User usermessage) { //登陆接口
User user=userMapper.login(usermessage.getUsername()); //根据用户名查用户信息
if(user==null){
return new Result(-1,"用户名错误,请重新输入",null);
}else{
if(user.getPassword().equals(usermessage.getPassword())){ //将传入的密码和查询到的密码进行比对
String token= Token.getToken(user.getPassword()); //生成token
user.setToken(token); //传递到user中一起传给前端
return new Result(1,"登陆成功",user);
}else{
return new Result(-1,"密码错误",null);
}
}
}
然后在前端接收到token并通过localStorage进行保存
login(){
this.$refs.aa.validate(a=>{
if(a){
this.http.post("/user/login",this.user).then(res=>{
if(res.code>0){
console.log(res.data)
localStorage.setItem("user",JSON.stringify(res.data)) //将数据存储到本地浏览器中
this.$router.push("/") //登陆成功
this.$message.success(res.msg)
}else{
this.$message.warning(res.msg) //登陆失败
}
})
}
})
},
然后在请求拦截器中将token进行添加, 这样每次请求就都会携带token了
const http=axios.create({
baseURL: '/x', //在这里加入前缀后,所有写的接口都会自动拼接上这个前缀, 你也可以在这里拼接上目标地址http://localhost:8080
//其实就是当你没有指定具体服务器地址的话,它就会拼接上当前vue运行的地址
timeout:3000 //规定最大请求时间为3秒
})
//request请求拦截器
http.interceptors.request.use(config=>{
config.headers["Content-Type"]="application/json;charset=utf-8" //添加请求头
var user=localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")):null
if(user){ //只有登陆了才会将用户信息user进行存储,这里就相当于判断是否登陆了
var token=user.token //获取后端生成的token
config.headers.token=user.token //在请求头中添加token
}
return config
},error=>{
return Promise.reject(error)
})
然后在后端设置拦截器用来验证token
//这是拦截器的具体的方法
@Controller
public class ProjectInterceptor implements HandlerInterceptor {
@Resource
private UserMapper userMapper;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//首先要确定访问的是一个方法
if(!(handler instanceof HandlerMethod)){ //这里表示当访问的是一个html页面的时候就直接结束,当访问的是一个方法的时候才会继续向下运行
//比如拦截"/**"请求,然后访问/aa.html此时就会直接结束,但是访问的是/aa这个方法的话,就会打印"开始拦截"
return true;
}
//然后判断请求中是否有token
String token=request.getHeader("token"); //从请求头中获取token
if(StrUtil.isBlank(token)){ //主要用来判断字符串类型的变量是否为空
throw new ServiceException(-1,"无token,请重新登陆");
}
//然后解析token中的载荷,但这一步无法判断token是否过期
String Username;
try {
List<String> ff = JWT.decode(token).getAudience(); //解析token中的主体中的数据,结果为["aa","bb","cc"],就是之前加密的那三个数据
Username= ff.get(0); //解析token从中获取载荷中的第一个数据,如果这串代码运行异常则说明该token字符串有问题
} catch (Exception e) {
throw new ServiceException(-1,"token验证失败,请重新登陆"); //此时说明token字符串已经被串改,导致解析失败
}
//通过用户名查询信息
User user=userMapper.login(Username); //根据解析的用户名查询数据
if(user==null){
throw new ServiceException(-1,"用户不存在,该token不合法");
}
//验证token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); //通过添加密钥来创建验证对象,从而验证token是否过期,因为即使过期了,上面也能解析到数据
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new ServiceException(-1,"token已过期,请重新登陆");
}
System.out.println("开始拦截");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
、
、
、
//这是注册上面的拦截器
@Controller
public class SpringMvcSupport implements WebMvcConfigurer {
@Resource
private ProjectInterceptor PI;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(PI).addPathPatterns("/**").excludePathPatterns("/NPA/**"); //这里是注册拦截器,并放行/NPA开头的请求
}
}
总体流程就是:
生成token--->前端保存token------>通过前端的拦截器给请求头添加token----->后端通过拦截器验证token。
验证token主要有:
1,请求头中是否有token
2,token是否被篡改,(也就是看是否能够正常的解析token)
3,token是否合法,(也就是看解析后的数据是否是你所保存的数据)
4,token是否过期,( 即使token过期了,也能正常的解析到数据)
登陆页面
<template>
<div class="box">
<!-- 登陆-->
<div class="container">
<h1 style="margin:20px 0;font-size:40px;"><b>登陆</b></h1>
<el-form :label-position="right" style="width:350px;margin:0 30px;" label-width="70px" :model="user" :rules="rules" ref="aa" >
<el-form-item label="用户名:" prop="username">
<el-input type="text" v-model="user.username" ></el-input>
</el-form-item>
<el-form-item label="密码:" prop="password">
<el-input show-password v-model="user.password"></el-input>
</el-form-item>
</el-form>
<div style="margin-bottom: 50px;">
<el-button type="primary" @click="login">登陆</el-button>
<el-button type="primary" @click="$router.push('/register')">前往注册</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data(){
return{
user:{}, //表单数据
rules:{
username:[
{required:true,message:'请输入用户名',trigger:"blur"},
{min:3,max:15,message:"字符在3到5之间"}
],
password:[
{required:true,message:"请输入密码",trigger:"blur"},
{max:15,message:"密码最大不超过15"}
]
}
}
},
methods:{
login(){
this.$refs.aa.validate(a=>{
if(a){
this.http.post("/NPV/login",this.user).then(res=>{
if(res.code>0){
console.log(res.data)
localStorage.setItem("user",JSON.stringify(res.data)) //将数据存储到本地浏览器中
localStorage.setItem("token",res.data.token) //保存token
this.$router.serRouter(); //设置路由
this.$router.push("/") //登陆成功
this.$message.success(res.msg)
}else{
this.$message.warning(res.msg) //登陆失败
}
})
}
})
},
}
}
</script>
<style scoped>
.box{
height:100vh;
overflow:hidden;
display:flex;
justify-content: center;
align-items:center;
background:linear-gradient(200deg,#f3e7e9,#e3eeff)
}
.container{
background:#fff;
border-radius: 15px;
display:flex;
flex-direction: column;
align-items:center;
}
</style>
注册
<template>
<div class="box">
<!-- 注册-->
<div class="container">
<h1 style="margin:20px 0;font-size:40px;"><b>注册</b></h1>
<el-form :model="user" :rules="rules" style="width:350px;margin:0 30px" ref="bb">
<el-form-item prop="username">
<el-input placeholder="用户名" type="text" v-model="user.username" ></el-input>
</el-form-item>
<el-form-item prop="email" style="position:relative">
<el-input placeholder="输入邮箱获取验证码" type="text" v-model="user.email" ></el-input>
<!-- 获取验证码-->
<el-button style="position: absolute;top:0;right:0;height:43px;" @click="validate">{{value}}</el-button>
</el-form-item>
<el-form-item prop="varification">
<el-input placeholder="输入验证码" show-password v-model="user.varification"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="密码" show-password v-model="user.password"></el-input>
</el-form-item>
<el-form-item prop="Confirmpassword">
<el-input placeholder="确认密码" show-password v-model="user.Confirmpassword"></el-input>
</el-form-item>
</el-form>
<div style="margin-bottom: 50px;">
<el-button type="primary" @click="register">注册</el-button>
<el-button type="primary" @click="$router.push('/login')">返回登陆</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data(){
return{
user:{}, //表单数据
value:"获取验证码",
rules:{
username:[
{required:true,message:'请输入用户名',trigger:"blur"},
{min:3,max:15,message:"字符在3到5之间"}
],
password:[
{required:true,message:"请输入密码",trigger:"blur"},
{max:15,message:"密码最大不超过15"}
],
Confirmpassword:[
{required:true,message:"请确认密码",trigger:"blur"},
{max:15,message:"密码最大不超过15"},
{validator:this.aa}
],
email:[
{required:true,message:"请输入邮箱",trigger:"blur"},
{type: 'email',message: "邮箱格式不正确"}
],
varification:[
{required:true,message:"请输入验证码"}
]
}
}
},
methods:{
aa(rule,val,callback){ //字段Confirmpassword的回调函数
if(val!==this.user.password){
callback(new Error("两次输入的密码不一致"))
}
return callback()
},
register(){ //注册
this.$refs.bb.validate(val=>{
if(val){
this.http.post("/NPV/register?varification="+this.user.varification,this.user).then(res=>{
console.log(res)
if(res.code>0){
this.$router.push("/login")
this.$message.success(res.msg)
}else{
this.$message.warning(res.msg)
}
}).catch(()=>{
this.$message.error("注册失败")
})
}
})
},
},
computed:{
validate(){ //获取验证码
var flag=true
return ()=>{
if(flag) {
var count = 60
this.value = count + "秒后重新获取"
flag=false
var that = this
this.http.get("/NPV/varification?email="+this.user.email)
var timer = setInterval(function () {
if (count > 0) {
count--
that.value= count + "秒后重新获取"
} else {
clearInterval(timer)
that.value = "请重新获取"
flag=true
}
}, 1000)
}
}
},
}
}
</script>
<style scoped>
.box{
height:100vh;
overflow:hidden;
display:flex;
justify-content: center;
align-items:center;
background:linear-gradient(200deg,#f3e7e9,#e3eeff)
}
.container{
background:#fff;
border-radius: 15px;
display:flex;
flex-direction: column;
align-items:center;
}
</style>
其实简单来说token就是一个具有时间期限的,可以让你拿到里面数据的字符串,
还有一种更简单的方式,结合redis,在登录的时候,自动生成一个字符串作为redis的键名并保存数据,设置保存在redis的时间为7天,
此时这个redis键名就可以作为token传给前端,前端在请求的时候,拦截器进行拦截并判断是否有token,有的话则通过该token从redis中查询数据,能查询到则该token就是合法的,则表示你已经正常登录了。
当然7天过后,你再通过这个token从redis中查询数据,将无法查询到,此时就会让你重新登录。
总之,token就是可以让你拿到数据的,具有时间期限的字符串