登录(JWT令牌、Filter过滤器、interceptor拦截器)
登录校验
-会话技术
-JWT令牌
-过滤器Filter
-拦截器Interceptor
会话
浏览器与服务器的一次连接就叫会话
登录就是一次请求,就是建立的会话
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
比如你登陆以后能看到很多登录后的信息
客户端会话跟踪技术:cookie
服务端会话跟踪技术:Session
令牌技术(当前企业最主流的技术)
cookie存储当前的用户名id存储在浏览器本地自动携带到服务端
服务器会自动的将cookie响应给浏览器。浏览器接收到响应的数据以后会自动的将cookie存储在浏览器本地。在后续的请求当中浏览器会自动将cookie携带到服务器端
cookie
优点:HTTP协议中支持的技术
缺点:
移动端APP无法使用Cookie
不安全,用户可以自己禁用cookie
Cookie不能跨域
session
优点:存储在服务端,安全
缺点:
服务器集群环境下无法直接使用Session
Cookie的缺点
令牌
优点:
支持PC端、移动端
解决集群环境下的认证问题
减轻服务器端存储压力
缺点:需要自己实现
令牌:字符串(用户身份的标识)
在客户端存储令牌
客户端将每一次请求都将令牌携带在身边,每一次服务器接受请求都会同意拦截。验证令牌的真伪
JWT令牌
header头 -- 载荷(自定义部分)-- 数字签名
这个令牌就是base64格式的解码
引入依赖
<dependency>
<groupld>io.jsonwebtoken</groupld>
<artifactld>jjwt</artifactld>
<version>0.9.1</version>
</dependency>
创建在utilsjwt令牌的工具类两个方法一个是生成令牌,一个是解析令牌(校验令牌)-
指定签名算法,然后是一个密钥:字符串
.设置自定义数据(载荷)map集合写入数据put
.设置有效期
.拿到字符串类型的返回值也就是jwt令牌
public class JwtUtils {
private static String signKey = "itheima";
private static Long expire = 43200000L;
public static String generateJwt (Map<String, Object> claims){
String jwt=Jwts.builder() //构建令牌
.setClaims(claims) //自定义内容(载荷)
.signWith(SignatureAlgorithm.HS256,signKey) //签名算法
.setExpiration(new Date(System.currentTimeMillis() + expire))//没置有效期为1h
.compact();
System.out.println(jwt);
return jwt;
}
}
parse:解析
JWT解码
public static Claims parseJwT(String jwt){
Claims claims = Jwts.parser()
.setsigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
System.out.println(claims)
return claims;
}
}
JWT是非常安全可靠的
过滤器Filter
概念: Filter 过滤器,是JavaWeb 三大组件(Servlet、Filter、Listener)之一
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
Filter快速入门(定义类、配置:两个注解)
1、定义Filter:定义一个类,实现 Filter 接口,并重写其所有方法
2、配置Filter: Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持
//首先标记为过滤器注解,还要在spap那个主键类启动类上面加注解那个@servletscan
//首先的filter接口是包javax.servlet
@WebFilter(urlPatterns = "/* ")
public class DemoFilter implements Filter {
public void init(EilterConfig filterConfig) throws ServletException{ //初始化方法,Web服务器启动,创建Filter时调用,只调用一次
Filter.super.init(filterConfig);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ //拦截到请求时,调用该方法。可调用多次
System.out.println("拦截方法执行,拦截到了请求...");
chain.doFilter(request, response);
}
public void destroy(){ //销毁服务器、服务器关闭时调用,只调用一次
Filter.super.destroy();
}
}
过滤器链
一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
浏览器 - > 请求 - > filter1 - 》 filter2 - > web资源
f2就只实现dofilter即可
拦截是1完了放行后,再拦截2再放行是2之后是首先是2放行后的逻辑然后才是1
这个过滤器的顺序是按照类名的自然顺序排名的
登录校验jwt流程
获取请求url。
判断请求url中是否包含login,如果包含,说明是登录操作,放行
获取请求头中的令牌 (token)
判断令牌是否存在,如果不存在,返回错误结果(未登录)
解析token,如果解析失败,返回错误结果 (未登录)
放行。
字符串转换工具
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
关于登录拦截器LoginCheckFilter.class
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoqinCheckFilter implements Filte{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException{
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.判断令牌是否存在,如果不存在,返回错误结果(未登录)
//使用spring提供的工具类StringUtils
//haslength这个字符串是否有长度
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT LOGIN");
//手动转换对象--json ---->阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
//getWriter获取输出流用write直接响应给浏览器
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"); //文档要求返回的提示msg
//手动转换 对象--json -----> 阿集巴巴fastJSON
String notLogin JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6.放行。
log.info("令牌合法,放行");
chain.doFilter(request, response);
拦截器interceptor
用来动态拦截控制器方法的执行
类似过滤器,spring内定的。在指定的方法调用前后,根据业务需要执行预先设定的代码
快速入门拦截器
1.定义拦截器,实现Handlerlnterceptor接口,并重写其所有方法。需要将这个类放入ioc容器 @component
2.注册拦截器
创建拦截器interceptor实现handler接口重写三个方法
单独创建个包存放配置类,存放注册拦截器
调用addinterceptor来注册拦截器,然后指定拦截什么的资源。
在过滤器中拦截所有资源配置是/*,但是在拦截器中拦截所有资源需要配置/**
详解(拦截路径、执行流程)
指定拦截哪些资源,也可以指定不拦截哪些资源
拦截路径 含义 举例
/* 一级路径 能匹配/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
//注册拦截器
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer{
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
//拦截路径的配置
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); //需要拦截/**不需要拦截/login
}
}
tomcat是servlet容器是不识别controller容器的,
拦截器的执行流程,过滤器先执行拦截,在过滤器执行了放行操作之后才放行给拦截器
接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现Handlerlnterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而interceptor只会拦截Spring环境中的资源
//定义拦截器
拦截器的步骤和登录校验的过滤器逻辑是完全一样的、而登录校验的逻辑应当定义在prehandle第一个方法当中
只需要将过滤器的校验登录流程全部复制到prehandle中即可,但不用前两行的格式强制转换,因为参数中传过来的即是servlet格式
多余的return true删除,记得修改形参名和逻辑步骤参数名一致
第二步骤是否包含login那直接 return true就直接放行了第四、五步骤不存在直接false
最后一步不需要dofilter直接return true即可
@Component
public class LoginCheckInterceptor implements HandlerInterceptor{
@override //目标资源方法执行前执行,放回true: 放行,返回false: 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception{
System.out.printIn("preHandle ...");
return true;
}
全局异常处理器
比如说添加相同的信息会报错
@RestControllerAdvice
public class GlobalExceptionHandler{
@ExceptionHandler(Exception.class) //代表捕获所有的异常
public Result ex(Exception ex){
ex.printStackTrace();
return Result.error(”对不起操作失败,请联系管理员“);
}
}