基于Token登录验证与统一拦截(一个JWT的Demo)

Posted 诺浅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Token登录验证与统一拦截(一个JWT的Demo)相关的知识,希望对你有一定的参考价值。

为什么要采用Token做登录验证

上一篇文章我们讲述了基于session+cookie的登录逻辑怎么做,这种模式在服务端为单体应用的且客户端为PC端浏览器是没有问题的,但是如果服务端做集群部署的话就需要考虑的session的统一存储,且这种模式不适用与APP端,毕竟APP端不会像浏览器那样自己管理cookie.此时采用JWT就是一个很好的选择,服务端无需存储token,也就更加适用于集群部署的情况,这也就是所说的无状态。

一个JWT的Demo

引入jwt依赖包

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.7.0</version>
</dependency>

编写一个jwt工具类,该工具类主要包含了以下方法

  1. 使用用户名和密码生成token方法
  2. 解密token得到用户名方法
public class JwtUtil 

	// 过期时间30分钟
	public static final long EXPIRE_TIME = 30 * 60 * 1000;

	/**
	 * 校验token是否正确
	 *
	 * @param token  密钥
	 * @param secret 用户的密码
	 * @return 是否正确
	 */
	public static boolean verify(String token, String username, String secret) 
		try 
			// 根据密码生成JWT效验器
			Algorithm algorithm = Algorithm.HMAC256(secret);
			JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
			// 效验TOKEN
			DecodedJWT jwt = verifier.verify(token);
			return true;
		 catch (Exception exception) 
			return false;
		
	

	/**
	 * 获得token中的信息无需secret解密也能获得
	 *
	 * @return token中包含的用户名
	 */
	public static String getUsername(String token) 
		try 
			if (token == null)
				throw new JeecgBootException("token不存在");
			
			DecodedJWT jwt = JWT.decode(token);
			return jwt.getClaim("username").asString();
		 catch (JWTDecodeException e) 
			return null;
		
	

	/**
	 * 生成签名,5min后过期
	 *
	 * @param username 用户名
	 * @param secret   用户的密码
	 * @return 加密的token
	 */
	public static String sign(String username, String secret) 
		Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
		Algorithm algorithm = Algorithm.HMAC256(secret);
		// 附带username信息
		return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);

	

	/**
	 * 根据request中的token获取用户账号
	 * 
	 * @param request
	 * @return
	 * @throws JeecgBootException
	 */
	public static String getUserNameByToken(HttpServletRequest request) throws JeecgBootException 
		String accessToken = request.getHeader("X-Access-Token");
		String username = getUsername(accessToken);
		if (oConvertUtils.isEmpty(username)) 
			throw new JeecgBootException("未获取到用户");
		
		return username;
	

编写登录controller,主要用户用户登录成功后调用jwt工具类生成token返回给浏览器端的方法

@RestController
public class LoginController 
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@RequestBody SysLoginModel sysLoginModel) throws Exception 
        String username = sysLoginModel.getUsername();
        String password = sysLoginModel.getPassword();
        // 通过查询数据库校验用户名是否存在,本处模拟数据库校验
        if (!"zhangsan".equals(username))
            return "用户不存在";
        
        // 校验密码是否正确
        if (!"12345".equals(password))
            return "密码不正确";
        
        // 生成token
        String token = JwtUtil.sign(username, password);

        // 把token放到redis里面并设置过期时间
        // redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
        // 设置超时时间
        // redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);

        return "登录成功,token为"+token;
    

接下来我们试着请求一下token

请求成功,下面来编写一个资源类

@RestController
public class HomeController 

    @RequestMapping("home")
    public String home()
        return "资源请求成功";
    

然后编写一个切面类来拦截未登录的请求

@Aspect
@Component
@Slf4j
public class LogAspect 

    // 定义切点Pointcut
    @Pointcut("execution(public * com.bxoon..*.*Controller.*(..))")
    public void excudeService() 
    

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable 
        long time1=System.currentTimeMillis();
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        String requestMethod = request.getMethod();
        String requestPath = request.getRequestURI().substring(request.getContextPath().length());
        requestPath = filterUrl(requestPath);
        log.info("拦截请求>>"+requestPath+";请求类型>>"+requestMethod);

        // 实际项目中可以考虑通过配置的方式配置不需要拦截的URL LIST
        if ("/login".equals(requestPath))
            Object result = pjp.proceed();
            long time2=System.currentTimeMillis();
            log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
            return result;
        
        try 
            String username = JwtUtil.getUserNameByToken(request);
        catch (JeecgBootException jeecgBootException)
            return jeecgBootException.getMessage();
        
        // 用户登录成功之后考虑把用户放到session里面或者redis里面,以便controller中获取当前用户
        Object result = pjp.proceed();
        long time2=System.currentTimeMillis();
        log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
        return result;
    

    private String filterUrl(String requestPath)
        String url = "";
        if(oConvertUtils.isNotEmpty(requestPath))
            url = requestPath.replace("\\\\", "/");
            url = requestPath.replace("//", "/");
            if(url.indexOf("//")>=0)
                url = filterUrl(url);
            
			/*if(url.startsWith("/"))
				url=url.substring(1);
			*/
        
        return url;
    

我们试着来请求一下资源路径http://localhost:8080/home

说明我们需要设置上一步获取的token

设置了token之后资源请求成功了。

完整Demo

篇幅所限,本文只例举关键代码,完整Demo连接Github

版权说明

部分代码摘抄自JEECG,如有侵权请告知,本文删除。

以上是关于基于Token登录验证与统一拦截(一个JWT的Demo)的主要内容,如果未能解决你的问题,请参考以下文章

微信一键登录—JWT生成token、登录拦截

Java实现Token登录验证(基于JWT的token认证实现)

实现基于JWT的Token登录验证功能

JWT鉴权如何来写一个token令牌认证登录?

掌握基于 JWT 实现的 Token 身份认证

如何在 Dotnet Core 中拦截请求 Token Jwt 以发送到其他 API?