基于JWT的权限验证及实战演练

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_28483283/article/details/82800954

前言:

大部分系统,除了大部分金融类的系统需要严格的安全框架(如shiro),一般的系统安全性要求都不是很高,只需要简单的权限验证(比如登录验证)即可,下面将简单介绍JWT用法及登录验证的实现方式(注解方式)

jwt分析(原理啊什么的废话就不说了,只说用法)

  1. header(头部),头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。(所以算法一样,头部也是一样的,下面例子可以看到)
  2. poyload(负荷),负荷基本就是自己想要存放的信息(因为信息会暴露,不应该在载荷里面加入任何敏感的数据)
  3. sign(签名),签名的作用就是为了防止恶意篡改数据
  4. 例子:eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3eHkifQ.l2CRkFWjXYWWUqe-_Qu4ANqx-jfxydSTN0vLGi9P51w以逗号分隔的,分为三部分
  5. 具体的jwt的解析可以去看 前后端分离之JWT用户认证这篇文章我看了,写的好不错,就其中提到的传统的seesion跟踪会话的方式做了比较,就我个人理解,下面在解释一下。

jwt登录验证和传统session验证的区别

  1. 传统的session验证存在安全问题,主要是因为session是存在cookie,具体弊端引用别人说的:

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被
JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS
传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

2.而jwt有什么特点呢?

  1. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或
    sessionStorage上,退出登录时前端删除保存的JWT即可。
  2. 简洁(Compact):可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
  3. 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)

cookie和localStorage又有什么区别呢?

Cookie 和 LocalStorage 比较
以上是两者对比,文章看了写的不错,具体有兴趣可以看看,下面对比一下主要区别:
在这里插入图片描述

jwt实战(登录校验)

上面说的废话好像有点多,主要是自己查阅资料后的一些总结和摘取。

引入JWT相关包

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

生成token

String token = JWT.create().withAudience(name) .sign(Algorithm.HMAC256(passwd));//生成token
redisTemplate.opsForHash().put("tokens", name, token);//存入redis

解释:以上操作是在用户点击登陆的时候,根据用户名已经密码生成的token,并且存入redis,前端登录以后服务器端会返回token,并且将其存在localStorage中
好处:存入redis的好处是redis可以设置定时删除,这就可以实现用户隔一段时间,token失效,从新登陆,从新生成token,进一步提高系统安全性,而且redis查询数据比数据库要快的多,而且会频繁查询,所以放在redis
图示:
在这里插入图片描述

登陆验证

现在token已经生成了,并且存入了redis,剩下的就是请求api时的登录验证了,如果验证失败就返回401(没有权限提示)
每次请求时,再请求头中都会携带token的信息,如下图:
在这里插入图片描述
接下来就是后端的事情了,后端采用的是在接口上面加上注解验证:
注解的具体实现方法是:

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate<String, ?> redis;
    @Value("${hashkey.web}")
    private String key;  
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 判断接口是否需要登录
        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
        HashRequired methodAnnotation2 = method.getAnnotation(HashRequired.class);
        CurrentUser methodAnnotation3 = method.getAnnotation(CurrentUser.class);
        // 有 @LoginRequired 注解,需要认证
        if (methodAnnotation != null) {
            // 执行认证
            String token = request.getHeader("authorization");  // 从 http 请求头中取出 token
            if (StringUtil.isEmpty(token)) {
            	response.sendError(401, "no header,try login !");
				return false;
            }
            String name=null;
			try {
				name = JWT.decode(token).getAudience().get(0);
				if (methodAnnotation3 != null) {
            		request.setAttribute("user", name);
            	}
			} catch (Exception e) {
				response.sendError(401, "wrong token!");
				return false;
			}
               // 验证token
            	if(!redis.opsForHash().hasKey("tokens", name)){
            		response.sendError(401, "no user token,try login!");
    				return false;
            	}
            	
            	String rtoken = redis.opsForHash().get("tokens", name).toString();
            	
            	if (!token.equals(rtoken)) {
            		response.sendError(401, "token invalid ,try login!");
    				return false;
				}
            return true;
        }
        if (methodAnnotation2 != null) {
            // 执行认证
        	String hashkey = request.getHeader("hashkey");
            if (StringUtil.isEmpty(hashkey)) {
            	response.sendError(401, "no key,reject !");
				return false;
            }
               // 验证hashkey
            if (!key.equals(hashkey)) {
            	response.sendError(401, "wrong key,reject !");
				return false;
			}            return true;
        }
        return true;
    }

    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
    }

    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }
}

解释:以上是一个全局请求拦截器,每次请求都会先判断这个,根据前端传来的token,将token解析,在到redis根据name取得原来登录存的token对比即可实现。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

解释:以上是LoginRequired 接口的实现
总结:项目采用的是springboot+springcloud+maven,基本上全部用的注解,如有问题,欢迎提出来。

猜你喜欢

转载自blog.csdn.net/qq_28483283/article/details/82800954
今日推荐