目录
本文章是基于Vue框架下的登录实现,运用了redis技术和不局限与游览器的loaclstora来存储登录信息
路由拦截器
首先运用路由拦截器,限制其他资源的访问
router.beforeEach((to, from, next) => {
//NProgress.start();
if (to.path == '/login') {
localStorage.removeItem('loginInfo');
}
let user = JSON.parse(localStorage.getItem('loginInfo'));
if (!user && to.path != '/login' && to.path != '/shopregist') {
next({ path: '/login' })
} else {
next()
}
})
登录界面进行请求访问后端
<template>
<el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px" class="demo-ruleForm login-container">
<h3 class="title">系统登录</h3>
<el-form-item prop="account">
<el-input type="text" v-model="ruleForm2.username" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="checkPass">
<el-input type="password" v-model="ruleForm2.password" auto-complete="off" placeholder="密码"></el-input>
</el-form-item>
<el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:47%;" @click.native.prevent="login" :loading="logining">登录</el-button>
<el-button type="primary" style="width:47%;" @click.native.prevent="shopregist" :loading="logining">商家入驻</el-button>
<!--<el-button @click.native.prevent="handleReset2">重置</el-button>-->
</el-form-item>
</el-form>
</template>
<script>
import { requestLogin } from '../api/api';
//import NProgress from 'nprogress'
export default {
data() {
return {
logining: false,
ruleForm2: {
username: '',
password: '',
loginType:null,
},
rules2: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
//{ validator: validaePass }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
//{ validator: validaePass2 }
]
},
checked: true
};
},
methods: {
shopregist(){
this.$router.push({ path: '/shopregist' });
},
handleReset2() {
this.$refs.ruleForm2.resetFields();
},
login() {
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
this.logining = true;
this.ruleForm2.loginType=0;
this.$http.post("/logininfo/account",this.ruleForm2)
.then(result=>{
this.logining = false;
result = result.data;
if(result.success)
{
//提示
this.$message({
message: "登录成功!",
type: 'success'
});
//把token和loginInfo存放到localStorage
let {token,loginInfo} = result.data;
localStorage.setItem("token",token);
//把对象转换为json字符串存放
localStorage.setItem("loginInfo",JSON.stringify(loginInfo));
//跳转主页
this.$router.push({ path: '/echarts' });
}else{
//提示
this.$message({
message: result.message,
type: 'error'
});
}
})
.catch(result=>{
this.logining = false;
this.$message({
message: "系统错误!",
type: 'error'
});
})
} else {
console.log('error submit!!');
return false;
}
});
}
}
}
</script>
后端通过通过拦截器放行
定义后端拦截器
package com.ronghua.user.interceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
* 登录拦截器
*
* @author wangling
* @date 2022/09/17
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果对用户有权限拦截,先对用户进行权限的判断,有权限在进行资源的访问 @TODO //运用springsecurity技术
String token = request.getHeader("token");//访问其他资源时,拿到请求中的token
if (!StringUtils.isEmpty(token)){//当token不为空的时候
Object obj = redisTemplate.opsForValue().get(token);//从redis中拿到token的值
if (obj!=null){//redis中拿到了token的值
/*因为前端给所有请求都加了token,也就是操作的时候都有token进来,所有每次进来都再次设置个30分钟,防止它在操作的时候突然过期了,结果访问下一个请求就又要重新登录*/
redisTemplate.opsForValue().set(token,obj,30, TimeUnit.MINUTES);
return true;
}
}
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=UTF-8");
PrintWriter writer = response.getWriter();
// 在外面写好在拷贝进去
writer.write("{\"success\":false,\"message\":\"noLogin\"}");
writer.close();
return false;
}
}
在启动类里面配置拦截器,通过实现WebMvcConfigurer添加拦截器
package com.ronghua;
import com.ronghua.user.interceptor.LoginInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//入口类测试
@SpringBootApplication
@MapperScan("com.ronghua.*.mapper")
//开启缓存注解
@EnableCaching
public class CarAppStart implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
public static void main(String[] args) {
SpringApplication.run(CarAppStart.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //拦截地址 一级二级都拦截
.excludePathPatterns("/logininfo/**")//放行登录
.excludePathPatterns("/user/register/**")//放行注册
.excludePathPatterns("/verifycode/**")//验证码
.excludePathPatterns("/fastDfs");//分布式文件服务器
}
}
登录真正实现
controller层---这里的Ajaxresult是自己编写的一个响应类
@PostMapping("/account")
public AjaxResult logingAccount(@RequestBody LoginDto loginDto){
try {
return logininfoService.logingAccount(loginDto);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("登录失败!"+e.getMessage());
}
}
service层
public AjaxResult logingAccount(LoginDto loginDto) {
String username = loginDto.getUsername();
String password = loginDto.getPassword();
Integer loginType = loginDto.getLoginType();
//1 校验
// 1.1 null判断
if (StringUtils.isEmpty(username)||StringUtils.isEmpty(password)||loginType==null)
return AjaxResult.me().setSuccess(false).setMessage("输入的信息不能为空");
// 1.2 判断用户是否存在 Username--username,phone,email
Logininfo logininfo=logininfoMapper.loadByName(loginDto);//数据库中查询此用户是否存在,这里可以直接通过用户名来查
if (logininfo==null)
return AjaxResult.me().setSuccess(false).setMessage("该用户不存在");
// 1.3 判断用户状态是否ok
if (logininfo.getDisable()==-1)
return AjaxResult.me().setSuccess(false).setMessage("该用户已经被冻结,请联系管理员");
//比对密码
String salt = logininfo.getSalt();//这是加密使用的盐值
String saltpassword = MD5Utils.encrypByMd5(password + salt);//通过MD5和盐值加密来对数据库的密码进行比对
if (!saltpassword.equals(logininfo.getPassword()))
return AjaxResult.me().setSuccess(false).setMessage("输入的密码不正确");
//3 记录logininfo到redis,key是token
String token = UUID.randomUUID().toString();//通过uuid创建一个token,这里的token是用来做axios的前置拦截用的
redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
//4 返回AjaxResult,如果登录成功,还需要返回token和loginInfo
Map<String,Object> map=new HashMap<>();
map.put("token", token);
//为了安全起见,把前台不会用到敏感信息salt和password去空
logininfo.setSalt(null);
logininfo.setPassword(null);
map.put("loginInfo", logininfo);
return AjaxResult.me().setData(map);
}
axios前置拦截器
为了安全起见,在前端发请求的时候加一个前置拦截器,访问其他资源请求时通过token来成功访问,我使用的是axios来发请求
import axios from 'axios'
axios.defaults.baseURL='http://localhost:80'
//==========axios的前置拦截=========
axios.interceptors.request.use(config=>{
let token = localStorage.getItem("token")
if(token){
config.headers["token"] = token;
}
return config;
},error => {
Promise.reject(error)
})
axios后置拦截器
如果localstorage里面没有token,也就是别人跳过密码攻击网站,localstorage里面是没有token就会通过后端的发送的失败信息,在通过前端的后置拦截器跳转的登录界面,并删除localstorage的信息。(后端拦截器已经配置了有token请求的访问放行)
axios.interceptors.response.use(config=>{
console.log("========")
let data = config.data;
if(!data.success && "noLogin"===data.message)
{
localStorage.removeItem("token");
localStorage.removeItem("loginInfo");
router.push({ path: '/login' });
}
console.log("========")
return config;
},error => {
Promise.reject(error)
})