登录功能
这个登陆功能先不返回JWT令牌
 
 登陆会返回JWT令牌
 一会在登陆验证时讲解JWT令牌(返回的data就是它)
 
 
 
登录校验
概述
就是你比如复制一个url
 用一个未曾登陆对应url系统的浏览器访问
 他会先进入登陆页面
 登陆校验就是实现这个功能
 简而言之,就是不能让你直接访问内部数据,要先登陆才可以
首先http协议是无状态的
 每次请求都是独立的
 而我们浏览器和web服务器之间就是http协议
 
 实现思路
 存一个登陆标记
 
 每个请求前有if判断对应队列标记
 登陆就正常执行,没有登陆就去登陆界面
 但是:这样太繁琐了
 
 所以我们使用统一拦截来做
 
对应技术主要介绍登陆标记(会话技术)和统一拦截呗
 
会话技术

 简而言之会话跟踪技术就是保证同一浏览器多个http请求之间能够数据共享
 一个会话可包含多个请求(这些请求之间数据共享)
 共享数据作用
 比如你更新验证码是一次请求,然后它是不是返回了结果
 然后你输入验证码登录,又是一次请求
 他需要验证你输入和上一次请求返回的数据是否相同
 so:需要请求之间的数据共享
 
 会话跟踪就是保证数据共享的核心技术
 有三种实现方式
 1.客户端Cookie
 2.服务器端session
 3.令牌技术(最常用)
会话跟踪技术-Cookie
cookie
 优点:http协议支持的
 当你进行请求的时候,如果服务端设置cookie
 会自动将cookie以及里面数据返回给浏览器-响应头
 然后浏览器会自动存储该cookie
 下次请求的时候会携带该cookie进行访问-请求头
 缺点:移动端和ios端不支持
 不安全:因为数据存储在浏览器,不能涉及一些隐私数据
 cookie不能跨域:因为现在一般是前后端分离部署
 你要访问前端页面,返回给你的cookie是不能在访问后端时候用的(或者说是不识别的)
 
先访问c1
 再访问c2
 看看能不能进行数据的同步
 
 如果响应头有set-cookie这个,浏览器会自动存储起来(因为我们设置了cookie,这是http协议支持的)
 
 存储在这里
 
 现在我们访问c2,会发现请求头带着set-cookie(我们原来接收到的cookie)来进行访问
 
会话跟踪技术-session
底层:
 session底层用cookie实现的
 当浏览器请求服务端,服务端建立一个session,每一个session有自己的id
 然后服务器端响应数据的时候会将session的id通过cookie响应给浏览器
 然后浏览器存储该session的id,每次请求带着这个session的id进行请求
 相当于session的id把set-Cookie在响应头和请求头的位置占了
 只不过换到了服务器端存储
 就可以通过session对象来实现请求数据间的共享
 优点就不说了
 缺点:底层是cookie的方式,所以cookie的缺点有
 而且现在一般是部署多台服务器,如果你第一次请求服务器转载给了1号服务器
 而第二次转请求带着对应id到了2号服务器,他就识别不到对应的session,即使识别到了也是错误的
 
还是测试一下
 s1和s2两个请求,一个session
 
 s1请求,响应头有session的id,然后浏览器对应的session的ID存储起来
 
 s2请求,带着session的id到请求头的set-cookie进行请求,且可以获取到s1的数据
 
会话跟踪技术-令牌
这种就是请求生成令牌
 令牌再反给浏览器,浏览器存储令牌==(令牌存储在浏览器(客户端)中)==
 (可以存储在cookie或者别的储存空间)
 浏览器带着令牌进行访问再通过拦截判断令牌有效性
 想要共享数据把对应数据存储在令牌中即可
1.由于可以存储在任何存储空间不止set-cookie
所以就可以在pc端移动端都可以用
2.服务器端不需要存储数据,就是服务器端是集群(分布式)
也可以使用,且不存储数据在服务器端,减轻服务器端压力
3.不用担心令牌伪造问题,具体在令牌技术讲解

 下面讲常用令牌技术JWT令牌
JWT令牌技术
介绍
三部分之间用.隔开
 前两部分是json形式存储后经过Base64编码成的字符串
 最后一部分签名是自动生成的根据前两部分(比如第一部分签名算法)并且加入指定秘钥(不是编码)
 第一部分是令牌类型和签名算法
 第二部分是我们的自定义信息和一些默认信息
 
 应用场景:登录认证
 
 就两部操作其实生成令牌和校验令牌
生成令牌和校验令牌
导入依赖,使用对应的工具类Jwts
 setExpiration()是设置有效期,单位是毫秒
 signWith()设置签名算法和秘钥
 setClaim()是设置自定义数据即json

 
对应的生成
 
 编码当然可以阶码,可以复制该数据到JWT官网即可解码(或者Base64解码工具)解码前两部分
 上面是签名算法,你用什么算法生成的选择什么就行
 
 如何基于Java代码校验JWT令牌
 指定签名秘钥和对应字符串即可解析(当然令牌过期、秘钥不对或者字符串不对会报错)
 so只要没有报错就是校验成功
 
 结果如图
 
下发和生成JWT令牌功能实现
看开发文档
 返回JWT令牌
 还说明了对应请求头名(这个是前端做的)
 我们只需要生成对应令牌返回给前端(Result里面的data)

 引入一个JWT工具类(自己定义的)
 两个方法
 1.生成令牌2.校验令牌
 指定两个属性
private static String signKey="ieheima";//秘钥是itheima
private static Long exprie=43200000L;//过期时间12个小时

 只需要改controller(因为只有controller是真正接收对应请求和客户端互动的)
 
 发送正确username和password会返回JWT令牌
 
 前后端联调一下
 登录完后的响应令牌

 前端将对应的数据存储在浏览器本地存储空间Local Storage
 
 然后下次请求就会带着这个JWT令牌
 如图一个新请求也会携带该令牌
 
统一拦截,校验令牌
就是对应的拦截器了
 这里就写两个目前企业常用的
 一个Filter一个Interceptor
过滤器Filter
快速入门
就是先过滤一遍
 然后从数据库取到数据还能再过滤一遍
 就是要经过两次Filter
 
快速入门
 1.定义一个类实现Filter接口和对应方法,上面写上注解@WebFilter(“”)来表明要拦截上面请求
 这里的话是/*就是所有请求都拦截
 2.在启动类上加上注解@ServletComponentScan
 因为Filter属于JavaWeb组件不是SpringBoot提供的
 
 注意:是java.servlet包里的Filter接口
 接口中就三个方法,初始化,拦截,和销毁
 初始化和销毁是有默认实现的,因为不常用
 这里就全实现了
 
拦截请求中需要进行放行操作
 放行需要调用参数chain里面的doFilter()方法,里面参数就是请求和响应
chain.doFilter(request,response);
详解

Filter执行流程
 经过两次Filter,但其实一次请求对应那个方法只会生效一次
 就是你放行完执行完对应的controller
 会直接到那个方法的下一步操作
 也就是放行后逻辑
 
 拦截路径
 注解WebFilter对应的urlPattern属性赋值
 
 没什么好说的
 过滤器链
 其实这个doFilter参数就有一个过滤器链
 就是需要经过多个过滤器
 如果还有过滤器就会放行给下一个过滤器
 如果没有就会到放行到web资源
 
 如果不指定过滤器顺序,默认按首字母排序
 
Filter实现登录校验

 
 对应的代码逻辑,稍微有点复杂
 可以看看
 这里因为要返回一个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);
    }
}

拦截器Interceptor
快速入门

 1.定义一个类实现拦截器接口HandlerInterceptor,实现对应方法
 2.配置拦截器,定义一个类实现WebMVCConfigurer,且用@Configuration注解
 表示是配置类
 重写addInterceptor方法注册拦截器到我们之前实现的类
 和指定对应拦截路径
 
 注意:
 preHandle的controller执行前的操作
 postHandle是controller执行后的操作
 afterCompletion是渲染完最后的操作
详解
拦截路径
 
 根据对应需求在配置文件注册拦截器的时候进行拦截路径的配置
 既能指定对于拦截路径,也能指定不拦截的路径
/*只能匹配一级路径,而/**可以匹配任意级路径
如/emp/*能匹配/emp/1但不能匹配/emp/1/2,而emp/*无论后面有多少级路径都可以匹配
执行流程
 先过滤器再拦截器
 
拦截器实现登录校验
实现思路是一样的
 
 对应的拦截逻辑类
@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...");
    }
}
当然我们还有对应的配置类进行拦截器注册
 
ok



















