登录校验、JWT令牌、Filter、Interceptor

发布时间:2023年12月17日

目录

登录功能

登录校验

会话技术

JWT令牌

过滤器Filter

拦截器Interceptor

异常处理

全局异常处理器


登录功能

登录-需求

登录-思路

?

登录-实现

?

登录-测试

联调测试

?

??

登录校验

什么是登录校验?

  • 所谓登录校验,指的是我们在服务器端接收到浏览器发送过来的请求之后,首先我们要对请求进行校验。先要校验一下用户登录了没有,如果用户已经登录了,就直接执行对应的业务操作就可以了;如果用户没有登录,此时就不允许他执行相关的业务操作,直接给前端响应一个错误的结果,最终跳转到登录页面,要求他登录成功之后,再来访问对应的数据。

那应该怎么来实现登录校验的操作呢?具体的实现思路可以分为两部分:

  1. 在员工登录成功后,需要将用户登录成功的信息存起来,记录用户已经登录成功的标记。

  2. 在浏览器发起请求时,需要在服务端进行统一拦截,拦截后进行登录校验。 ?

?

?

会话技术

会话技术介绍

什么是会话?

  • 在我们日常生活当中,会话指的就是谈话、交谈

  • 在web开发当中,会话指的就是浏览器与服务器之间的一次连接,我们就称为一次会话

在用户打开浏览器第一次访问服务器的时候,这个会话就建立了,直到有任何一方断开连接,此时会话就结束了。在一次会话当中,是可以包含多次请求和响应的。

比如:打开了浏览器来访问web服务器上的资源(浏览器不能关闭、服务器不能断开)

  • 第1次:访问的是登录的接口,完成登录操作

  • 第2次:访问的是部门管理接口,查询所有部门数据

  • 第3次:访问的是员工管理接口,查询员工数据

只要浏览器和服务器都没有关闭,以上3次请求都属于一次会话当中完成的?

?

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪技术有两种:

  1. Cookie(客户端会话跟踪技术)

    • 数据存储在客户端浏览器当中

  2. Session(服务端会话跟踪技术)

    • 数据存储在储在服务端

  3. 令牌技术

会话跟踪方案对比?

?

?

针对于这三种方案,现在企业开发当中使用的最多的就是第三种令牌技术进行会话跟踪。而前面的这两种传统的方案,现在企业项目开发当中已经很少使用了

JWT令牌

全称:JSON? Web? Token官网

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的

JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)

  • 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{"alg":"HS256","type":"JWT"}

  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{"id":"1","username":"Tom"}

  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

?

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码

Jwt应用场景:登录

① 登录成功后,生成令牌

② 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

?

生成和校验

要想使用JWT令牌,需要先引入JWT的依赖:

<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

在引入完JWT来赖后,就可以调用工具包中提供的API来完成JWT令牌的生成和校验

工具类:Jwts

    @Test
    public void testGenJwt(){
        Map<String, Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("name","tom");
        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "green")//签名算法
                .addClaims(claims)//自定义内容(载荷)
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期1小时
                .compact();
        log.info("jwt令牌为:{}",jwt);
    }

?运行测试方法:

jwt令牌为:eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcwMjUzNDM4MX0.9saoCEXfKXbXHI1FrdAoa2E4DspKi_H662-2oNIGEis

校验JWT令牌(解析生成的令牌):

    @Test
    public void testParseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("green")
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcwMjUzNDM4MX0.9saoCEXfKXbXHI1FrdAoa2E4DspKi_H662-2oNIGEis")
                .getBody();
        System.out.println(claims);
    }

运行测试方法:

{name=tom, id=1, exp=1702534381}

?

过滤器Filter

?

快速入门

  • 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。

  • 第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。

定义过滤器

@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行了");
    }

    @Override //拦截到请求之后调用, 调用多次
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了请求...放行前逻辑");
        //放行
        chain.doFilter(request,response);
    }

    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
    }
}
@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagementApplication.class, args);
    }

}

注意事项:在过滤器Filter中,如果不执行放行操作,将无法访问后面的资源。 放行操作:chain.doFilter(request, response);

Filter详解(执行流程、拦截路径、过滤器链)

?

?

Filter拦截路径

?

拦截路径

urlPatterns

含义

拦截具体路径

/login

只有访问 /login 路径时,才会被拦截

目录拦截

/emps/*

访问/emps下的所有资源,都会被拦截

拦截所有

/*

访问所有资源,都会被拦截

?过滤器链

?

总结?
1.执行流程
请求 --> 放行前逻辑 --> 放行 --> 资源 --> 放行后逻辑
2.拦截路径
/login、/depts/*、/*
3.过滤器链
?一个web应用中,配置了多个过滤器,就形成了一个过滤器链

登录校验-Filter

?

?

?

代码实现

  • 基本信息

    请求路径:/login
    
    请求方式:POST
    ?
    接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。 
  • 请求参数

    参数格式:application/json

    参数说明:

    名称类型是否必须备注
    usernamestring必须用户名
    passwordstring必须密码

    请求数据样例:

    {
        "username": "jinyong",
     ? ?"password": "123456"
    }
  • 响应数据

    参数格式:application/json

    参数说明:

    名称类型是否必须默认值备注其他信息
    codenumber必须响应码, 1 成功 ; 0 失败
    msgstring非必须提示信息
    datastring必须返回的数据 , jwt令牌

    响应数据样例:

{
 ?"code": 1,
 ?"msg": "success",
 ?"data": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"
}
  • 备注说明

    用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为 token ,值为 登录时下发的JWT令牌。

    如果检测到用户未登录,则会返回如下固定错误信息:

{
    "code": 0,
    "msg": "NOT_LOGIN",
    "data": null
}

登录校验过滤器:LoginCheckFilter

@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        //前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //1.获取请求url
        String url = request.getRequestURL().toString();
        log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("/login")){
            chain.doFilter(request, response);//放行请求
            return;//结束当前方法的执行
        }

        //3.获取请求头中的令牌(token)
        String token = request.getHeader("token");
        log.info("从请求头中获取的令牌:{}",token);

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(token)){
            log.info("Token不存在");

            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(token);
        }catch (Exception e){
            log.info("令牌解析失败!");

            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return;
        }

        //6.放行
        chain.doFilter(request, response);
    }
}

?

拦截器Interceptor

?

?

快速入门

  1. 定义拦截器

  2. 注册配置拦截器

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法 ?

//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行。 返回true:放行    返回false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        return true; //true表示放行
    }

    //目标资源方法执行后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle ... ");
    }

    //视图渲染完毕后执行,最后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion .... ");
    }
}

注意:

preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行

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

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

注册配置拦截器:实现WebMvcConfigurer接口,并重写addInterceptors方法 ?

@Configuration  
public class WebConfig implements WebMvcConfigurer {

    //自定义的拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
    }
}

Interceptor详解(拦截路径、执行流程)

?拦截器-拦截路径

拦截路径

含义

举例

/*

一级路径

能匹配/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

?拦截器-执行流程

?

登录校验- Interceptor

?

登录校验拦截器

//自定义拦截器
@Component //当前拦截器对象由Spring创建和管理
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    //前置方式
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        //1.获取请求url
        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行

        //3.获取请求头中的令牌(token)
        String token = request.getHeader("token");
        log.info("从请求头中获取的令牌:{}",token);

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(token)){
            log.info("Token不存在");

            //创建响应结果对象
            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            //设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return false;//不放行
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(token);
        }catch (Exception e){
            log.info("令牌解析失败!");

            //创建响应结果对象
            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            //设置响应头
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return false;
        }

        //6.放行
        return true;
    }

?注册配置拦截器

@Configuration  
public class WebConfig implements WebMvcConfigurer {
    //拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

?

异常处理

?

?

当我们没有做任何的异常处理时,我们三层架构处理异常的方案:

  • Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。

  • service 中也存在异常了,会抛给controller。

  • 而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。

解决方案

  • 方案一:在所有Controller的所有方法中进行try…catch处理

    • 缺点:代码臃肿(不推荐)

  • 方案二:全局异常处理器

    • 好处:简单、优雅(推荐)

全局异常处理器

怎么样定义全局异常处理器?

  • 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。

  • 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。

@RestControllerAdvice
public class GlobalExceptionHandler {

    //处理异常
    @ExceptionHandler(Exception.class) //指定能够处理的异常类型
    public Result ex(Exception e){
        e.printStackTrace();//打印堆栈中的异常信息
        //捕获到异常之后,响应一个标准的Result
        return Result.error("对不起,操作失败,请联系管理员");
    }
}

?

?来源:黑马程序员2023新版JavaWeb开发教程,实现javaweb企业开发全流程(涵盖Spring+MyBatis+SpringMVC+SpringBoot等)

文章来源:https://blog.csdn.net/weixin_52270382/article/details/134992606
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。