前后端分离使用token实现会话技术

后台用户登录技术与实现


前言

传统项目是在登录后要把登录状态保持下来,就用到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()
  }
})

总结

提示:文章内容可能有问题或者不全,欢迎留言指正

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值