关于token,token登录,token登录置换退出

@TOC

1、基本概念

为什么要使用token

优点

  1. 后台不用保存token,只需要验证是否是自己签发的token
  2. 支持多种前段,如移动端和浏览器

缺点和解决方法

每次都要去数据库查询权限信息验证token
解决:
将查询到的权限数据保存到session中,之后可以直接从session中获取
也可以使用redisJ解决

2、代码实操

tokenVo

过期时间:方便前端人员判断是否置换,在快过期但用户又有新操作时需要置换

package com.bean.vo;
import java.io.Serializable;
/**
 * 返回前端-Token相关VO
 */
public class TokenVO implements Serializable {

	/** 用户认证凭据 */
	private String token;
	/** 过期时间 */
	//通常是总毫秒数: token生成时间+有效时长
	private long expTime;
	/** 生成时间 */
	//通常是总毫秒数
	private long genTime;

	public String getToken() {
		return token;
	}
	public void setToken(String token) {
		this.token = token;
	}
	public long getExpTime() {
		return expTime;
	}
	public void setExpTime(long expTime) {
		this.expTime = expTime;
	}
	public long getGenTime() {
		return genTime;
	}
	public void setGenTime(long genTime) {
		this.genTime = genTime;
	}
	
	public TokenVO() {
		super();
	}
	public TokenVO(String token, long expTime, long genTime) {
		super();
		this.token = token;
		this.expTime = expTime;
		this.genTime = genTime;
	}
}

2.1、前端

localStorage(可以将token保存到这里)
res.data是dto的data属性

 <script type="text/javascript">
	  $(".submit").bind("click", function () {
		  $.post(
		  		"/auth/login",
				$("#actionForm").serialize(),
			    function (res) {
					//判断
					if (res.success == "true"){ //登录成功
						//将token保存到localStorage
						localStorage.token = res.data.token;
						//保存token过期时间
						localStorage.tokenExpire = res.data.expTime;
						//跳转页面
						location = "/page/main.html";
					}else {
						//显示错误提示
						$(".info").html(res.msg);
					}
				}
		  );
	  })
  </script>
package com.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ViewController {

    @RequestMapping("/")
    public String base(){
        System.out.println(">>> base");
        return "/page/login.html";
    }
}

TokenUtil.java

session时自动刷新,而token要前端来请求,所以token的置换剩余时间少于1个小时
UserAgentInfo类来自于辅助工具依赖 的插件

package com.util;

import com.alibaba.fastjson.JSON;
import cz.mallat.uasparser.UserAgentInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

@Component
public class TokenUtil {

    @Autowired
    private RedisUtil redisUtil;

    private int session_timeout = 60 * 60 * 2; //token有效时长

    private int replacement_protection_timeout = 60 * 60; //token置换保护时长

    private int replacement_delay= 60 * 2; //旧token延迟过期时间

    private String token_prefix = "token:";//token前缀

    /** 获取token默认有效时长 */
    public int getTimeout(){
        return session_timeout;
    }

    /** 获取登录时间的总毫秒数 */
    public long getLogin(String token) throws ParseException {
        //拆分token
        String[] arr = token.split("[-]");

        return new SimpleDateFormat("yyyyMMddHHmmss").parse(arr[3]).getTime();
    }

    /** token是否存在 */
    public boolean exists(String token){
        return redisUtil.exists(token);
    }

    /**
     * 保存token
     * @param token
     * @param user
     */
    public void save(String token, Object user) {
        //判断, token是否以"token:PC-"开头
        if (token.startsWith(token_prefix + "PC-")){
            //PC端登录有效期2小时
            redisUtil.setString(token, JSON.toJSONString(user), session_timeout);
        }else {
            //移动端登录永久有效
            redisUtil.setString(token, JSON.toJSONString(user));
        }
    }

    /**
     * 读取token中的对象
     * @param token
     * @param clazz
     * @return
     */
    public Object load(String token, Class clazz){
        return JSON.parseObject(redisUtil.getString(token), clazz);
    }

    /**
     * 删除token
     * @param token
     */
    public void delete(String token) throws Exception {
        //判断, token在redis中是否存在
        if (!redisUtil.exists(token))
            throw new Exception();
        //删除token
        redisUtil.delete(token);
    }

    /**
     * 生成token
     * @param name 用户账号
     * @param id 用户id
     * @param agent 设备信息
     * @return token<br>
     *     格式:<br>
     *         PC端: 前缀PC-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent<br>
     *         移动端: 前缀MOBLIE-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent
     */
    public String generateToken(String name, Object id, String agent){

        StringBuilder sb = new StringBuilder();

        //添加token前缀
        sb.append(token_prefix);

        //添加设备类型
        try {
            //获取终端类型
            String deviceType = getDeviceType(agent);
            //添加到token
            sb.append(deviceType + "-");
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        //添加账号(32位加密)
        sb.append(MD5.getMd5(name, 32) + "-");

        //添加id
        sb.append(id + "-");

        //添加登录时间, 格式yyyyMMddHHmmss
        sb.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-");

        //添加设备信息(6加密)
        sb.append(MD5.getMd5(agent, 6));

        return sb.toString();
    }

    /**
     * 验证token
     * @param token
     * @param agent 设备信息
     * @param type 验证类型<br>
     *     1: 基本验证. 包含结果1,11,12,13,20
     *     2: 置换验证. 包含结果1,11,12,14
     * @return 结果数值<br>
     *     11: token不存在<br>
     *     12: token格式有误<br>
     *     13: token已超时<br>
     *     14: token仍在保护期<br>
     *     20: 其它token异常<br>
     *     1: token正常
     */
    public int validate(String token, String agent, String type){
        // token不存在
        if (!exists(token)) return 11;
        //拆分token字符串
        String[] tokenDetails = token.split("-");
        //获取token生成时间的总毫秒数
        long genTime = 0;
        try {
           genTime = getLogin(token);
        } catch (ParseException e) {
            e.printStackTrace();
            return 12;
        }
        //计算登录时长
        long passed = Calendar.getInstance().getTimeInMillis() - genTime;
        System.out.println(passed);
        System.out.println(passed / 1000 / 60);
        //判断, 验证类型
        if (type.equals("1")){
            //判断, 是否已超时
            if (passed > session_timeout * 1000) return 13;
            //获取登录设备加密信息
            String agentMD5 = tokenDetails[4];
            //判断, 当前设备是否与登录设备一致
            if(MD5.getMd5(agent, 6).equals(agentMD5)) return 1;
        }else {
            //判断, 是否处于置换保护期内
            if (passed < replacement_protection_timeout * 1000) return 14;
            return 1;
        }

        return 20;
    }

    /**
     * 置换token
     * @param token
     * @param agent 设备信息
     * @param clazz 置换对象的类型
     * @param nameFieldName 账号属性名
     * @param idFiledName id属性名
     * @return
     */
    public String replaceToken(String token, String agent, Class clazz, String nameFieldName, String idFiledName) {
        //获取旧token中存储的用户数据
        Object user = load(token, clazz);
        System.out.println(user);

        //获取旧token有效期(剩余秒数 )
        long ttl = redisUtil.getExpire(token);
//        System.out.println("ttl:" + ttl);
        //判断token是否仍在有效期内容
//        if (ttl > 0 || ttl == -1) {
            Object name = ""; //用户账号
            Object id = ""; //用户id
            try {
                //获取用户账号
                Method getName = clazz.getDeclaredMethod(nameFieldName);
                name = getName.invoke(user);
                //获取用户id
                Method getId = clazz.getDeclaredMethod(idFiledName);
                id = getId.invoke(user);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            //生成新token
            String newToken = this.generateToken(name.toString(), id.toString(), agent);
            //保存新token
            this.save(newToken, user);
            //设置2分钟后旧token过期,注意手机端由永久有效变为2分钟后失效
            redisUtil.setString(token, JSON.toJSONString(user), replacement_delay);

            return newToken;
//        }

//        return null;
    }

    /**
     * 获取设备类型
     * @param agent
     * @return
     */
    public String getDeviceType(String agent) throws IOException {
        //用户设备信息对象
        UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(agent);
        //判断, 设备类型是否未知
        if (userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)) {
            //判断, 已知设备类型是否为移动端
            if (UserAgentUtil.CheckAgent(agent)) return "MOBILE";
            //已知设备不是移动端, 则为PC端
            else return "PC";
        }
        //判断, 设备类型是否为PC端
        else if (userAgentInfo.getDeviceType().equals("Personal computer")) return "PC";
        //其他类型均识别为移动端
        else return "MOBILE";
    }
}

控制类

登录方法

 @PostMapping("/login")
    @ResponseBody
    public Dto login(User user, HttpServletRequest request){
        System.out.println(">>> 用户登录");
        System.out.println(user);

        /* 数据验证(略) */

        /* 登录查询 */
        List<User> userList = userService.find(user);

        /* 处理查询结果 */
        //判断, 集合大小
        if (userList.size() != 1)
            return new Dto("用户名或密码错误!", "100001");

        //获取登录用户对象
        user = userList.get(0);

        //判断, 账号状态
        if (user.getStatus() == 0)
            return new Dto("账号未激活, 请先激活账号!", "100002");

        //登录成功,就要生成token
        String token = tokenUtil.generateToken(user.getUserName(), user.getId(),
                request.getHeader("user-agent"));

        //保存token
        tokenUtil.save(token, user);

        try {
            //获取tokenVo
            TokenVO tokenVO = new TokenVO(token,
                    tokenUtil.getLogin(token) + tokenUtil.getTimeout()*1000,
                    tokenUtil.getLogin(token));

            return new Dto(tokenVO);

        } catch (ParseException e) {
            e.printStackTrace();
            return new Dto("token格式有误!", "100003");
        }

    }

    @PostMapping("/registe/mail")
    @ResponseBody
    public Dto registeByEmail(User user){
        System.out.println(">>> 用户注册--邮箱");
        System.out.println(user);

        //添加用户
        userService.addUserByMail(user);

        return new Dto("注册成功, 请尽快激活账号!");
    }

启动启动类
启动nginx和redis
就可以访问了

获取header中的token

有时候我们登录之后要去token中拿数据
在这里插入图片描述

 @GetMapping("/login/info")
    @ResponseBody
    public Dto getLoginInfo(HttpServletRequest request){
        System.out.println(">>> 获取登录用户信息");

        //获取header中的token
        String token = request.getHeader("token");
        System.out.println(token);

        //验证token是否正常
        int result = tokenUtil.validate(token, request.getHeader("user-agent"), "1");

        //根据验证结果的所有异常
        switch (result){
            case 11: 
                return new Dto("token不存在", "200011");
            case 12:
                return new Dto("token格式有误", "200012");
            case 13:
                return new Dto("token已超时", "200013");
            case 20:
                return new Dto("token异常", "200020");
        }

        //获取token中的对象
        User user = (User) tokenUtil.load(token, User.class);

        //返回
        return new Dto(user);
    }

前端
每次需要登录向后台请求都要带上 headers

	  /* 加载登录用户信息 */
	  $.ajax({
	     type: "get",
		 url: "/auth/login/info",
		 success: function (res) {
			 if (res.success == "true") {
				 $("#loginUserName").html(res.data.realName);
			 }else {
				 $("#loginUserName").html("游客(未登录)");
			 }
		 },
	     headers: {
		 	token: localStorage.token
		 }
	  });

置换token,退出

置换

前端需要判断token是否过期,如果过期就需要置换(如果需要登录才能访问的情况下)

前端

	 function parseToken(token){
		  //拆分token
		  var arr = token.split("-");
		  //获取年
		  var year = arr[3].substring(0, 4);
		  //获取月
		  var month = arr[3].substring(4, 6);
		  //获取日
		  var date = arr[3].substring(6, 8);
		  //获取时
		  var hours = arr[3].substring(8, 10);
		  //获取分
		  var min = arr[3].substring(10, 12);
		  //获取秒
		  var sec = arr[3].substring(12);

		  return new Date(year, month-1, date, hours, min, sec);
		  // alert(d);
	  }

	  replaceToken();

	  //置换token
	  function replaceToken() {
		  //获取token
		  var token = localStorage.token;
		  //获取过期时间
		  var expTime = localStorage.tokenExpire;
		  // alert(expTime)
		  // var date = parseToken(token);

		  //获取剩余登录时间的毫秒数
		  var loginTimeLeft =  expTime - new Date().getTime();
		  // alert(loginTimeLeft < 1000 * 60 * 60);
		  //判断, 是否需要置换token
		  if (loginTimeLeft < 1000 * 60 * 60){
		  	$.ajax({
				headers: {
					token: localStorage.token
				},
				type: "get",
				url: "/auth/token/replace",
				success: function (res) {
					if (res.success == "true"){
						localStorage.token = res.data.token;
						localStorage.tokenExpire = res.data.expTime;
					}
					// else{
					// 	alert(res.msg);
					// }
				}
			});
		  }
	  }

控制类

 @GetMapping("/token/replace")
    @ResponseBody
    public Dto replaceToken(HttpServletRequest request){
        System.out.println(">>> 置换token");

        //获取header中的token
        String token = request.getHeader("token");
        System.out.println(token);

        //验证token是否正常
        int result = tokenUtil.validate(token, null,"2");

        try {
//            System.out.println(tokenUtil.getLogin(token));
            System.out.println(new Date(tokenUtil.getLogin(token)));
            System.out.println(tokenUtil.getTimeout());
        } catch (ParseException e) {
            e.printStackTrace();
        }

        //根据验证结果的所有异常
        switch (result){
            case 11:
                return new Dto("token不存在", "200011");
            case 12:
                return new Dto("token格式有误", "200012");
            case 14:
                return new Dto("token已超时", "200013");
        }

        //置换token, 获取新token
        token = tokenUtil.replaceToken(token, request.getHeader("user-agent"),
                User.class, "userName", "id");

        //判断, 新token
        if (token == null)
            return new Dto("token置换失败!", "200030");



        try {
            TokenVO tokenVO = new TokenVO(token,
                    tokenUtil.getLogin(token) + tokenUtil.getTimeout()*1000,
                    tokenUtil.getLogin(token));

            return new Dto(tokenVO);

        } catch (ParseException e) {
            e.printStackTrace();
            return new Dto("token格式有误!", "100003");
        }
    }

    @GetMapping("/login/info")
    @ResponseBody
    public Dto getLoginInfo(HttpServletRequest request){
        System.out.println(">>> 获取登录用户信息");

        //获取header中的token
        String token = request.getHeader("token");
        System.out.println(token);

        //验证token是否正常
        int result = tokenUtil.validate(token, request.getHeader("user-agent"), "1");

        //根据验证结果的所有异常
        switch (result){
            case 11:
                return new Dto("token不存在", "200011");
            case 12:
                return new Dto("token格式有误", "200012");
            case 13:
                return new Dto("token已超时", "200013");
            case 20:
                return new Dto("token异常", "200020");
        }

        //获取token中的对象
        User user = (User) tokenUtil.load(token, User.class);

        //返回
        return new Dto(user);
    }

退出

验证,是否是我记录的token,以及是否是同个设备
前端

 <a href="javascript:void(0)" id="logoff">退出</a>
 function replaceToken() {
		
	  $("#logoff").bind("click", function () {

	  	if (!confirm("确定退出系统吗?")) return;

		$.ajax({
			headers: {
				token: localStorage.token
			},
		  	url: "/auth/logoff",
			type: "get",
			success: function (res) {
				if (res.success == "true") {
				#清除
					localStorage.token = null;
					localStorage.tokenExpire = null;
					location = "/page/login.html";
				}
			}
		});
  })

后台

 @GetMapping("/logoff")
    @ResponseBody
    public Dto logoff(HttpServletRequest request){
        System.out.println(">>> 退出登录");

        //获取header中的token
        String token = request.getHeader("token");
        System.out.println(token);

        //删除token
        try {
            tokenUtil.delete(token);
        } catch (Exception e) {
            e.printStackTrace();
            return new Dto("token不存在!", "100011");
        }

        return new Dto("退出成功!");
    }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值