登录与登录验证

登录功能

对于没有登录管理系统的用户,这些用户是不可以直接对其它的资源进行访问的。但是目前我们并没有做到要对用户进行拦截的操作。总共有这几种方法

  • jwttoken验证+Filter 过滤
  • jwtInterceptor拦截器拦截

JWT令牌

JWT令牌就是一个经过长编码后的一个字符串,其内容主要可以分为

  • payload
  • 签名算法
  • 过期时间

我们可以使用JWTS工具来生成一个jwt令牌

1
2
3
4
5
6
    String jwt = Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS256, signKey)
            .setExpiration(new Date(System.currentTimeMillis() + expireTime))
            .compact();
    return jwt;

其中,Claims是指定要进行填充的字段,其类型是Map<String, Object>数据类型,第二个参数就是去指定签名算法和签名的key。第三个参数就是去设置这个令牌的有效时间,最后把他们打包为一个字符串即可。

同理,我们也可以解析一个JWT令牌

1
2
3
4
5
6
7
    public static Claims parseJwt(String jwt) {
        Claims body = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return body;
    }

只要指定好这个生成JWT时对应的签名,那么就可以得到这个JWT令牌,并且,如果这个JWT已经过期或者签名被篡改,那么在解析的过程中就会引发异常,因此可以在校验JWT是否合法中起到作用,对应的,如果在校验过程中引发异常,那么就可能是下面这几种情况

  • JWT令牌过期
  • JWT令牌错误

可以用来进行对用户的校验。

Filter过滤器

可以使用过滤器来过滤请求。

实现Filter接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@WebFilter(urlPatterns = "/login") // 拦截器需要拦截的路径
public class DemoFilter implements Filter {

    // 初始化方法,只调用一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter开始调用了");
    }

    // 拦截时调用该方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("放行前的逻辑");
        // 放行逻辑
        // 如果不写下面这行放行逻辑的代码,那么数据其实就一直处于被拦截的状态
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("放行后的逻辑");
    }

    // 销毁方法,只执行一次
    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

这里总共是需要重写三个方法,其中的doFilter对应我们的拦截逻辑。除此之外,我们还需要加上一个@WebFilter接口,里面可以指定要拦截的url

实现登录验证

使用Filter来进行拦截,一种可行的思路是:无论是哪种方法,我们都进行拦截,只不过在拦截时进行区分。

  • 如果当前方法是login方法,那么我们直接放行,让用户登录即可。

  • 如果是非login方法,我们需要对用户进行JWT令牌校验

    • 首先JWT令牌在登录后服务器会返回给前端浏览器
    • 浏览器每次请求都会在header里面携带这个JWT令牌

    如果在验证过程中发生异常,那么说明JWT令牌存在问题,我们不能让该请求通过

  • 只有JWT验证成功时才可以登录。

将拦截器的操作全部封装在doFilter里面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 获取请求的url
        String urlRequest = request.getRequestURL().toString();
        if (urlRequest.contains("login")) {
            // 直接放行
            filterChain.doFilter(request, response);
            return;
        }

        // 此时拦截到的就是非login请求
        // 获取token
        String token = request.getHeader("token");
        // 使用jwt对token进行校验
        log.info("token:{}", token);

        if (!StringUtils.hasLength(token)) {
            log.info("token不存在");
            // 将这个信息返回回去
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);
            return;
        }

        // 令牌存在
        // 将令牌进行解析
        try {
            JwtUtil.parseJwt(token);
        } catch (Exception e) {
            log.info("token解析失败");
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);
        }

        // 如果不发生错误,那么就在这里放行
        filterChain.doFilter(request, response);

    }

所以其拦截思路是:

  • 拦截所有的请求,login操作除外
  • 拦截到请求后,检验JWTJWT检验无误后放行

Interceptor拦截

需要实现HandlerInterceptor接口,同时重写这个函数里面的方法。

1
2
3
4
5
preHandle方法目标资源方法执行前执行 返回true放行    返回false不放行

	postHandle方法目标资源方法执行后执行

	afterCompletion方法视图渲染完毕后执行最后执行

这里我们只需要重写preHandle方法即可,注意方法返回true代表是放行操作,返回false就是不放行。

demo

逻辑几乎相同,不过在不放行时选择返回false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 返回值为True表示放行
        // 返回值为False表示不放行
        // 实现登录拦截
        // 获取请求的url
        log.info("Interceptor....");
        String urlRequest = request.getRequestURL().toString();
        if (urlRequest.contains("login")) {
            // 直接放行
            return true;
        }
        // 其它操作
        String token = request.getHeader("token");
        if (!StringUtils.hasLength(token)) {
            log.info("token不存在");
            // 将这个信息返回回去
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);// token不存在
            return false;
        }
        // 检验token
        try {
            JwtUtil.parseJwt(token);
        }catch (Exception e){
            log.info("token不存在");
            // 将这个信息返回回去
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);
            return false;
        }
        return true;
    }

注册WebConfig

1
2
3
4
5
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }

添加路径

在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:

拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配 /depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

以上就是拦截器的执行流程。通过执行流程分析,大家应该已经清楚了过滤器和拦截器之间的区别,其实它们之间的区别主要是两点:

  • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
  • 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

总结

我们使用两种方式实现了登录校验功能,一般来说,对于api之类的请求,我们可以直接使用Interceptor来实现~

Licensed under CC BY-NC-SA 4.0
花有重开日,人无再少年
使用 Hugo 构建
主题 StackJimmy 设计