后台用户登录技术与实现
文章目录
前言
传统项目是在登录后要把登录状态保持下来,就用到tomcat的会话跟踪技术:session,而是依赖于cookie里面jsessionid;
一、传统项目的登录
以上的技术不适合前后端分离的环境;所以在前后端分离环境,我们有2种方案:方案一:Ngnix代理 方案二: token方案
二、无状态方案-token方案
1. 这里就使用Redis来实现存储token,和会话过期;
准备:
1.pom的配置:
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.yml的配置
#redis集群
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 20000
# 集群环境打开下面注释,单机不需要打开
# cluster:
# 集群信息
# nodes: xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx
# #默认值是5 一般当此值设置过大时,容易报:Too many Cluster redirections
# maxRedirects: 3
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
password:
application:
name: spring-boot-redis
注意:要启动Redis服务(命令):
redis-server.exe redis.windows.conf
2.Java和vue实现
1.后台登录接口
controller层代码:
@RestController
@RequestMapping("/login")
public class LoginInfoController {
@Autowired
private ILoginInfoService loginInfoService;
@PostMapping("/account")
public MessageResult accountLogin(@RequestBody LoginInfoDto loginInfoDto){
try {
MessageResult messageResult = loginInfoService.accountLogin(loginInfoDto);
return messageResult;
} catch (Exception e) {
e.printStackTrace();
return MessageResult.returnMessage().setSuccess(false).setDescription("登录失败!");
}
}
}
service层代码
@Service
public class LoginInfoServiceImpl extends BaseServiceImpl<LoginInfo> implements ILoginInfoService {
@Autowired
private LoginInfoMapper loginInfoMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public MessageResult accountLogin(LoginInfoDto loginInfoDto) {
/*
3.验证用户名,密码,用户类型不能为空;
4.查询用户是否存在,不存在就提醒用户(用户名或密码错误)
5.用户存在:用MD5比对密码
6.以上都没问题:就使用UUID生成一个token,并存如redis,设置过期时间;再把token和用户信息返回给前端,密码置空*/
String username = loginInfoDto.getUsername();
String password = loginInfoDto.getPassword();
Integer loginType = loginInfoDto.getLoginType();
//验证用户名,密码,用户类型不能为空;
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return MessageResult.returnMessage().setSuccess(false).setDescription("用户名或密码不能为空!");
}
if (!StringUtils.hasLength(loginType.toString())){
return MessageResult.returnMessage().setSuccess(false).setDescription("系统错误,请稍后再试!");
}
//询员工是否存在,不存在就提醒用户(用户名或密码错误)
LoginInfo loginInfo = loginInfoMapper.findByLoginInfo(loginInfoDto);
if (null == loginInfo){
return MessageResult.returnMessage().setSuccess(false).setDescription("用户名或密码错误!");
}
if (loginInfo.getDisable()!=1){
return MessageResult.returnMessage().setSuccess(false).setDescription("该用户已被禁用!");
}
//用户存在:用MD5比对密码
//对输入的密码进行加密
String inputPassword = MD5Utils.encrypByMd5(loginInfoDto.getPassword() + loginInfo.getSalt());
if (!inputPassword.equals(loginInfo.getPassword())){
return MessageResult.returnMessage().setSuccess(false).setDescription("用户名或密码错误!");
}
//以上都没问题:就使用UUID生成一个token,并存如redis,设置过期时间;再把token和用户信息返回给前端,密码置空
String token = UUID.randomUUID().toString();
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set(token,loginInfo,30, TimeUnit.MINUTES);//30分钟后过期
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("token",token);
loginInfo.setPassword("");
tokenMap.put("user",loginInfo);
return MessageResult.returnMessage().setDescription("登录成功!").setObj(tokenMap);
}
}
2.前台登录实现并且保存user和token到localStorage
前端把后台返回的token和用户信息,保存在localStorage里面,因为sessionStorage只在当前窗口有效;
this.$http.post('/login/account',loginParams).then((res) => {
this.logining = false;
console.log(res,"dddddddddddd")
let data = res.data;
if (!data.success){
this.$message({
message: data.description,
type: 'error'
});
}else {
let {token,user} = data.obj;
localStorage.setItem("token",token);
localStorage.setItem("user",JSON.stringify(user));
this.$router.push({ path: '/echarts' });
}
});
3.前台通过axios的前置拦截器携带token到后台
前端所有的请求,都会先到这里,才会到后台;所以这里使用:localStorage.getItem("token");获取到token赋值给请求头;
import axios from 'axios'
//配置axios的全局基本路径
axios.defaults.baseURL='http://localhost:8080';
//全局属性配置,在任意组件内可以使用this.$http获取axios对象
Vue.prototype.$http = axios;
//------------前置拦截,发送请求前的拦截
axios.interceptors.request.use(config=>{
//携带token
let token = localStorage.getItem("token");
if(token){
// alert(token)
config.headers["token"]=token;
}
return config;
},error => {
Promise.reject(error);
});
4.后台做token的登录拦截器,如果没有回报错给前台
创建一个登录拦截器,来获取token,判断token是否过期,再决定是否对请求放行;
//登录拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取前端的前置拦截器里面设置的token,在request里面获取
String token = request.getHeader("token");
if (false && StringUtils.hasLength(token)){
//去redis中获取,查看是否存在,不存在就重新登录
Object tokenValue = redisTemplate.opsForValue().get(token);
if (null != tokenValue){
redisTemplate.opsForValue().set(token,tokenValue,30, TimeUnit.MINUTES);
return true;
}
}
//前端没有传token
response.setCharacterEncoding("utf-8");//乱码
response.setContentType("application/json;charset=utf-8");//返回类型json
PrintWriter writer = response.getWriter();
writer.write("{\"success\":false,\"msg\":\"logout\"}");
writer.flush();
writer.close();
return false;
}
}
5.前台通过axios后置拦截器对后台登录拦截错误进行跳转到登录页面
//-----------------后置拦截器
axios.interceptors.response.use(config=>{
if (!config.data.success && config.data.msg==='logout'){
router.push({ path: '/login'});
}
return config;
},error => {
Promise.reject(error);
})
6.前台也要做拦截-有的地址是不需要访问后台
router.beforeEach((to, from, next) => {
//NProgress.start();
if (to.path == '/login') {
localStorage.removeItem('user');
localStorage.removeItem('token');
next()
}
let user = JSON.parse(localStorage.getItem('user'));
if (!user && to.path != '/login') {
next({ path: '/login' })
} else {
// console.log(user)
next()
}
})
总结
提示:文章内容可能有问题或者不全,欢迎留言指正