Token

1.Token是什么

Token是一个字符串,是一段根据特殊规则生成的唯一的、保存在缓存服务器(如Redis)中的key,这个key对应的value是用户账户数据。每个用户登录后,服务器生成一个类似Token并缓存,之后用户每次请求中都要带上这个Token,以便实现类似于HTTP Session的会话跟踪。

2.为什么要用Token

随着近几年上网设备的多样化,软件项目的UI层不仅仅再是窗体、html、还有iOS、安卓app,因此基于Servlet的HTTP Session也就不再适用,因为可能没有浏览器和html页面,也没有cookie了。这种情况下、就需要Token技术来完成用户的跟踪,使得一个账户可以从html登录,也可以从手机app登其它客户端登录。

3.Token如何生成

每个HTTP数据包括两大主要部分,头部字段和消息体,Header是对一个HTTP数据包的描述信息,一般有多个字段和值。为了对不同客户端的Token区别对待,生成Token时需要判断客户端类型,拼接不同前缀。为了保证Token的唯一性和保密性,生成Token时还要拼接用户名+ID+日期时间+6位密码(盐,salt根据客户端信息生成);代码如下

    /**
     * 为了保证Token的唯一性和保密性,生成Token时还要拼接用户名(加密)+ID+日期时间+6位密码(盐,salt根据客户端信息生成);
     * @param agent
     * @param user
     * @return null
     */
    @Override
    public String generateToKen(String agent, ItripUser user) {
        try {//UserAgentUtil可检测客户端类型,因为要根据客户端的类型生成不同的token
            UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(agent);
            StringBuilder sb = new StringBuilder();
            sb.append(tokenPrefix);
            if(userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)){//未知客户端类型
                if(UserAgentUtil.CheckAgent(agent)){
                    sb.append("MOBILE-");//如果包含移动设备的关键字,拼接移动设备的Token前缀
                }else {
                    sb.append("PC-");//拼接PC的Token前缀
                }
            }else if(userAgentInfo.getDeviceType().equals("Personal computer")){
                sb.append("PC-");//如果很明显是PC类型,拼接PC的TOken的前缀
            }else{
                sb.append("MOBILE-");//拼接移动设备的Token前缀
            }
            sb.append(MD5.getMd5(user.getUserCode(),32) + "-");//拼接加密用户名称
            sb.append(user.getId() + "-");//拼接user id及日期
            sb.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-");
            sb.append(MD5.getMd5(agent,6));//在拼接6个字符的密码
            return sb.toString();
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }

4.Token登录与退出

登录业务方法

    /**
     * 登录
     * @return 登录成功的user对象,失败返回null
     * @throws Exception
     */
    @Override
    public ItripUser login(String name, String password) throws Exception {
        ItripUser user = this.findByUsername(name);
        if(null != user&&user.getUserPassword().equals(password)){
            if(user.getActivated() != 1){
                throw new UserLoginFailedException("用户未激活");
            }
            return user;
        }
        return null;
    }

保存Token到Redis缓存

    /**
     * TokenServiceImpl中,保存Token到Redis缓存服务器
     * @param token
     * @param user
     */
    @Override
    public void save(String token, ItripUser user) {
        if(token.startsWith(tokenPrefix+"PC-")){
            //如果客户端是PC,Token有过期时间
            redisAPI.set(token,expire, JSON.toJSONString(user));
        }else {
            //客户端是手机,Token永不过期
            redisAPI.set(token,JSON.toJSONString(user));
        }
    }

登录方法接收前端请求

  /**
     * 登陆方法接收前端请求
     * @param name
     * @param password
     * @param request
     * @return
     */
    @RequestMapping(value = "/dologin",method = RequestMethod.POST,produces = "application/json")
    @ResponseBody
    public Dto dologin(@RequestParam String name, @RequestParam String password,
                       HttpServletRequest request){
        if(!EmptyUtils.isEmpty(name) && !EmptyUtils.isEmpty(password)){
            ItripUser user = null;
            try {   //调用service进行登录
                user = userService.login(name.trim(),MD5.getMd5(password.trim(),32));
            }catch (UserLoginFailedException e){//返回登录失败错误
                return DtoUtil.returnFail(e.getMessage(),ErrorCode.AUTH_AUTHENTICATION_FAILED);
            }catch (Exception e){
                e.printStackTrace();
                return DtoUtil.returnFail(e.getMessage(),ErrorCode.AUTH_UNKNOWN);//返回未知错误
            }
            if(EmptyUtils.isNotEmpty(user)){//如果用户不为空,表示登录成功
                String token = tokenService.generateToKen(
                        request.getHeader("user-agent"),user);//调用service生成Token
                tokenService.save(token,user);//保存token到redis缓存
                //计算token过期时间毫秒数,为系统当前事件毫秒数+7200s*1000毫秒/s,即2小时
                long timeout = Calendar.getInstance().getTimeInMillis()+ToKenService.SESSION_TIMEOUT*1000;
                //创建要返回的ItripTokenVO
                ItripTokenVO tokenVO = new ItripTokenVO(token,timeout,Calendar.getInstance().getTimeInMillis());
                //tokenVo转为JSON并响应给客户端
                return DtoUtil.returnDataSuccess(tokenVO);
            }else{
                return DtoUtil.returnFail("用户名密码错误",ErrorCode.AUTH_AUTHENTICATION_FAILED);
            }
        }else{
            return DtoUtil.returnFail("参数错误!检查提交的参数名是否正确。",ErrorCode.AUTH_PARAMETER_ERROR);
        }
    }

验证token

    /**
     * 验证token是否有效:1.token在redis中是否存在;2.token是否过期
     */
    @Override
    public boolean validate(String agent, String token) {
        boolean tokenValid = false; //该变量表示token是否有效
        if(!redisAPI.exist(token)){ //如果token在redis不存在
            return tokenValid;      //返回false
        }
        try {
            String [] tokenDatails = token.split("-");//按-分割token字符串
            SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
            Date TokenGenTime = formatter.parse(tokenDatails[3]);//还原token生成时间
            long passed = Calendar.getInstance().getTimeInMillis() - TokenGenTime.getTime();//计算token已生成多久时间(毫秒数)
            if(passed > this.SESSION_TIMEOUT * 1000){//如果token已经过期
                return tokenValid; //返回false
            }
            String agentMD5 = tokenDatails[4];
            if(MD5.getMd5(agent,6).equals(agentMD5)){   //验证token的6位密码                 
                tokenValid = true;//如果token中的密码也一致,token有效
            }
        }catch (ParseException e){
            e.printStackTrace();
        }
        return tokenValid;
    }

删除Token

    /**
     * 从redis删除token(key)和用户信息(value)
     * @param token
     */
    @Override
    public void delete(String token) {
        if(redisAPI.exist(token)){
            redisAPI.delete(token);
        }
    }

退出登录方法

    @RequestMapping(value = "/logout",method = RequestMethod.GET,produces = "application/json",headers = "token")
    @ResponseBody
    public Dto logout(HttpServletRequest request){
        //从请求的Header中获取token
        String token = request.getHeader("token");
        //删除token和信息
        try {
            //验证Redis中是否有该token(且没有过期)
            if(!tokenService.validate(request.getHeader("user-agent"),token)) {
                return DtoUtil.returnFail("token无效", ErrorCode.AUTH_TOKEN_INVALID);
            }else{
                tokenService.delete(token);//从Redis删除
                return DtoUtil.returnSuccess("注销成功!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return DtoUtil.returnFail("注销失败!", ErrorCode.AUTH_UNKNOWN);
        }
    }

 

  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值