登录功能
这个登陆功能先不返回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