Web-登录功能实现(含JWT令牌)

登录功能

这个登陆功能先不返回JWT令牌
Web-登录功能实现(含JWT令牌)_第1张图片
登陆会返回JWT令牌
一会在登陆验证时讲解JWT令牌(返回的data就是它)
Web-登录功能实现(含JWT令牌)_第2张图片
Web-登录功能实现(含JWT令牌)_第3张图片
Web-登录功能实现(含JWT令牌)_第4张图片

登录校验

概述

就是你比如复制一个url
用一个未曾登陆对应url系统的浏览器访问
他会先进入登陆页面
登陆校验就是实现这个功能
简而言之,就是不能让你直接访问内部数据,要先登陆才可以

首先http协议是无状态的
每次请求都是独立的
而我们浏览器和web服务器之间就是http协议
Web-登录功能实现(含JWT令牌)_第5张图片
实现思路
存一个登陆标记
Web-登录功能实现(含JWT令牌)_第6张图片
每个请求前有if判断对应队列标记
登陆就正常执行,没有登陆就去登陆界面
但是:这样太繁琐了
Web-登录功能实现(含JWT令牌)_第7张图片
所以我们使用统一拦截来做
Web-登录功能实现(含JWT令牌)_第8张图片

对应技术主要介绍登陆标记(会话技术)和统一拦截呗
Web-登录功能实现(含JWT令牌)_第9张图片

会话技术

在这里插入图片描述
简而言之会话跟踪技术就是保证同一浏览器多个http请求之间能够数据共享
一个会话可包含多个请求(这些请求之间数据共享)
共享数据作用
比如你更新验证码是一次请求,然后它是不是返回了结果
然后你输入验证码登录,又是一次请求
他需要验证你输入和上一次请求返回的数据是否相同
so:需要请求之间的数据共享
Web-登录功能实现(含JWT令牌)_第10张图片
会话跟踪就是保证数据共享的核心技术
有三种实现方式
1.客户端Cookie
2.服务器端session
3.令牌技术(最常用)

会话跟踪技术-Cookie

cookie
优点:http协议支持的
当你进行请求的时候,如果服务端设置cookie
会自动将cookie以及里面数据返回给浏览器-响应头
然后浏览器会自动存储该cookie
下次请求的时候会携带该cookie进行访问-请求头
缺点:移动端和ios端不支持
不安全:因为数据存储在浏览器,不能涉及一些隐私数据
cookie不能跨域:因为现在一般是前后端分离部署
你要访问前端页面,返回给你的cookie是不能在访问后端时候用的(或者说是不识别的)
Web-登录功能实现(含JWT令牌)_第11张图片

先访问c1
再访问c2
看看能不能进行数据的同步
Web-登录功能实现(含JWT令牌)_第12张图片
如果响应头有set-cookie这个,浏览器会自动存储起来(因为我们设置了cookie,这是http协议支持的)
Web-登录功能实现(含JWT令牌)_第13张图片
存储在这里
Web-登录功能实现(含JWT令牌)_第14张图片
现在我们访问c2,会发现请求头带着set-cookie(我们原来接收到的cookie)来进行访问
Web-登录功能实现(含JWT令牌)_第15张图片

会话跟踪技术-session

底层:
session底层用cookie实现的
当浏览器请求服务端,服务端建立一个session,每一个session有自己的id
然后服务器端响应数据的时候会将session的id通过cookie响应给浏览器
然后浏览器存储该session的id,每次请求带着这个session的id进行请求
相当于session的id把set-Cookie在响应头和请求头的位置占了
只不过换到了服务器端存储
就可以通过session对象来实现请求数据间的共享
优点就不说了
缺点:底层是cookie的方式,所以cookie的缺点有
而且现在一般是部署多台服务器,如果你第一次请求服务器转载给了1号服务器
而第二次转请求带着对应id到了2号服务器,他就识别不到对应的session,即使识别到了也是错误的

Web-登录功能实现(含JWT令牌)_第16张图片

还是测试一下
s1和s2两个请求,一个session
Web-登录功能实现(含JWT令牌)_第17张图片
s1请求,响应头有session的id,然后浏览器对应的session的ID存储起来
Web-登录功能实现(含JWT令牌)_第18张图片
s2请求,带着session的id到请求头的set-cookie进行请求,且可以获取到s1的数据
Web-登录功能实现(含JWT令牌)_第19张图片

会话跟踪技术-令牌

这种就是请求生成令牌
令牌再反给浏览器,浏览器存储令牌==(令牌存储在浏览器(客户端)中)==
(可以存储在cookie或者别的储存空间)
浏览器带着令牌进行访问再通过拦截判断令牌有效性
想要共享数据把对应数据存储在令牌中即可

1.由于可以存储在任何存储空间不止set-cookie
所以就可以在pc端移动端都可以用
2.服务器端不需要存储数据,就是服务器端是集群(分布式)
也可以使用,且不存储数据在服务器端,减轻服务器端压力
3.不用担心令牌伪造问题,具体在令牌技术讲解

Web-登录功能实现(含JWT令牌)_第20张图片
下面讲常用令牌技术JWT令牌

JWT令牌技术

介绍

三部分之间用.隔开
前两部分是json形式存储后经过Base64编码成的字符串
最后一部分签名是自动生成的根据前两部分(比如第一部分签名算法)并且加入指定秘钥(不是编码)
第一部分是令牌类型和签名算法
第二部分是我们的自定义信息和一些默认信息
Web-登录功能实现(含JWT令牌)_第21张图片
应用场景:登录认证
Web-登录功能实现(含JWT令牌)_第22张图片
就两部操作其实生成令牌和校验令牌

生成令牌和校验令牌

导入依赖,使用对应的工具类Jwts
setExpiration()是设置有效期,单位是毫秒
signWith()设置签名算法和秘钥
setClaim()是设置自定义数据即json

Web-登录功能实现(含JWT令牌)_第23张图片
Web-登录功能实现(含JWT令牌)_第24张图片

对应的生成
Web-登录功能实现(含JWT令牌)_第25张图片
编码当然可以阶码,可以复制该数据到JWT官网即可解码(或者Base64解码工具)解码前两部分
上面是签名算法,你用什么算法生成的选择什么就行
Web-登录功能实现(含JWT令牌)_第26张图片
如何基于Java代码校验JWT令牌
指定签名秘钥和对应字符串即可解析(当然令牌过期、秘钥不对或者字符串不对会报错)
so只要没有报错就是校验成功
Web-登录功能实现(含JWT令牌)_第27张图片
结果如图
Web-登录功能实现(含JWT令牌)_第28张图片

下发和生成JWT令牌功能实现

看开发文档
返回JWT令牌
还说明了对应请求头名(这个是前端做的)
我们只需要生成对应令牌返回给前端(Result里面的data)

Web-登录功能实现(含JWT令牌)_第29张图片
引入一个JWT工具类(自己定义的)
两个方法
1.生成令牌2.校验令牌
指定两个属性

private static String signKey="ieheima";//秘钥是itheima
private static Long exprie=43200000L;//过期时间12个小时

Web-登录功能实现(含JWT令牌)_第30张图片
只需要改controller(因为只有controller是真正接收对应请求和客户端互动的)
Web-登录功能实现(含JWT令牌)_第31张图片
发送正确username和password会返回JWT令牌
Web-登录功能实现(含JWT令牌)_第32张图片
前后端联调一下
登录完后的响应令牌

Web-登录功能实现(含JWT令牌)_第33张图片
前端将对应的数据存储在浏览器本地存储空间Local Storage
Web-登录功能实现(含JWT令牌)_第34张图片
然后下次请求就会带着这个JWT令牌
如图一个新请求也会携带该令牌
Web-登录功能实现(含JWT令牌)_第35张图片

统一拦截,校验令牌

就是对应的拦截器了
这里就写两个目前企业常用的
一个Filter一个Interceptor

过滤器Filter

快速入门

就是先过滤一遍
然后从数据库取到数据还能再过滤一遍
就是要经过两次Filter
Web-登录功能实现(含JWT令牌)_第36张图片

快速入门
1.定义一个类实现Filter接口和对应方法,上面写上注解@WebFilter(“”)来表明要拦截上面请求
这里的话是/*就是所有请求都拦截
2.在启动类上加上注解@ServletComponentScan
因为Filter属于JavaWeb组件不是SpringBoot提供的
Web-登录功能实现(含JWT令牌)_第37张图片
注意:是java.servlet包里的Filter接口
接口中就三个方法,初始化,拦截,和销毁
初始化和销毁是有默认实现的,因为不常用
这里就全实现了
Web-登录功能实现(含JWT令牌)_第38张图片

拦截请求中需要进行放行操作
放行需要调用参数chain里面的doFilter()方法,里面参数就是请求和响应

chain.doFilter(request,response);

详解

Web-登录功能实现(含JWT令牌)_第39张图片

Filter执行流程
经过两次Filter,但其实一次请求对应那个方法只会生效一次
就是你放行完执行完对应的controller
会直接到那个方法的下一步操作
也就是放行后逻辑
Web-登录功能实现(含JWT令牌)_第40张图片
拦截路径
注解WebFilter对应的urlPattern属性赋值
Web-登录功能实现(含JWT令牌)_第41张图片
没什么好说的
过滤器链
其实这个doFilter参数就有一个过滤器链
就是需要经过多个过滤器
如果还有过滤器就会放行给下一个过滤器
如果没有就会到放行到web资源
在这里插入图片描述
如果不指定过滤器顺序,默认按首字母排序
Web-登录功能实现(含JWT令牌)_第42张图片

Filter实现登录校验

Web-登录功能实现(含JWT令牌)_第43张图片
Web-登录功能实现(含JWT令牌)_第44张图片
对应的代码逻辑,稍微有点复杂
可以看看
这里因为要返回一个json的数据格式
需要用到albb的fastJson依赖,记得添加

@Slf4j
//@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        //1.获取请求url。
        String url = req.getRequestURL().toString();
        log.info("请求的url: {}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if(url.contains("login")){
            log.info("登录操作, 放行...");
            chain.doFilter(request,response);
            return;
        }

        //3.获取请求头中的令牌(token)。
        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
        if(!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {//jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败, 返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //6.放行。
        log.info("令牌合法, 放行");
        chain.doFilter(request, response);

    }
}

Web-登录功能实现(含JWT令牌)_第45张图片

拦截器Interceptor

快速入门

Web-登录功能实现(含JWT令牌)_第46张图片
1.定义一个类实现拦截器接口HandlerInterceptor,实现对应方法
2.配置拦截器,定义一个类实现WebMVCConfigurer,且用@Configuration注解
表示是配置类
重写addInterceptor方法注册拦截器到我们之前实现的类
和指定对应拦截路径

Web-登录功能实现(含JWT令牌)_第47张图片
注意:
preHandle的controller执行前的操作
postHandle是controller执行后的操作
afterCompletion是渲染完最后的操作

详解

拦截路径
Web-登录功能实现(含JWT令牌)_第48张图片
根据对应需求在配置文件注册拦截器的时候进行拦截路径的配置
既能指定对于拦截路径,也能指定不拦截的路径

/*只能匹配一级路径,而/**可以匹配任意级路径
如/emp/*能匹配/emp/1但不能匹配/emp/1/2,而emp/*无论后面有多少级路径都可以匹配

执行流程
先过滤器再拦截器
Web-登录功能实现(含JWT令牌)_第49张图片

拦截器实现登录校验

实现思路是一样的
Web-登录功能实现(含JWT令牌)_第50张图片
对应的拦截逻辑

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        //1.获取请求url。
        String url = req.getRequestURL().toString();
        log.info("请求的url: {}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if(url.contains("login")){
            log.info("登录操作, 放行...");
            return true;
        }

        //3.获取请求头中的令牌(token)。
        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
        if(!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {//jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败, 返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }

        //6.放行。
        log.info("令牌合法, 放行");
        return 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...");
    }
}

当然我们还有对应的配置类进行拦截器注册
Web-登录功能实现(含JWT令牌)_第51张图片

异常处理

有时出现异常
比如你添加一个相同名称的部门
但是部门的名称是用unique修饰的,你的sql语句就会报错
此时返回的错误信息不是Result
而是一个别的json
Web-登录功能实现(含JWT令牌)_第52张图片
Web-登录功能实现(含JWT令牌)_第53张图片
对应的异常抛出到controller层
最开始想到的也是在controller每个方法进行try catch操作
但是太繁琐
所以就出现了全局异常处理器

全局异常处理器

Web-登录功能实现(含JWT令牌)_第54张图片
@RestControllerAdvice
以用来修饰类,表示该类为一个全局异常处理类
@ExpectionHandler()后面指定捕获哪一类的异常
Exception就是捕获所有异常
定义完后再进行对应操作
就是标准的Result结果
Web-登录功能实现(含JWT令牌)_第55张图片
Web-登录功能实现(含JWT令牌)_第56张图片

你可能感兴趣的:(JavaWeb,前端,java)