**
认证流程
**
前端通过Web表单将自己的用户名和密码发送到后端的接口。该过程一般是HTTP的POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。
后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage(浏览器本地缓存)或sessionStorage(session缓存)上,退出登录时前端删除保存的JWT即可。
前端在每次请求时将JWT放入HTTP的Header中的Authorization位。(解决XSS和XSRF问题)HEADER
后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确﹔检查Token是否过期;检查Token的接收方是否是自己(可选)
验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
**
JWT结构
**
就是令牌token,是一个String字符串,由3部分组成,中间用点隔开
令牌组成:
标头(Header)
有效载荷(Payload)
签名(Signature)
token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz
Header:有令牌的类型和所使用的签名算法,如HMAC、SHA256、RSA;使用Base64编码组成;(Base64是一种编码,不是一种加密过程,可以被翻译成原来的样子)
{
“alg” : “HS256”,
“type” : “JWT”
}
Payload :有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码
{
“sub” : “123”,
“name” : “John Do”,
“admin” : true
}
Signature :前面两部分都使用Base64进行编码,前端可以解开知道里面的信息。Signature需要使用编码后的header和payload,加上我们提供的一个密钥,使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过
**
引入依赖
**
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--toJson-->
因为filter不是spring框架提供的,所以返回Result类结果不会自动转为JSON格式,所以引入依赖来手动转换。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
**
Filter过滤基本框架
**
@WebFilter(urlPatterns = "/*")//过滤的路径,/*代表接受全部路径
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//放行 访问资源
filterChain.doFilter(servletRequest,servletResponse);
//资源访问后还会回到本方法,并执行放行之后的逻辑
//多个过滤器会产生过滤器链,访问过滤器顺序按照过滤器名字的字母排序
System.out.println("after doFilter");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
定义一个filter实现Filter接口,有三个重写函数,初始化init,过滤操作doFilter,销毁操作destroy。
在doFilter中实现放行操作代码
filterChain.doFilter(servletRequest,servletResponse);
**
运用filter和jwt实现登录操作
**
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest) servletRequest;
HttpServletResponse res=(HttpServletResponse) servletResponse;
//获取url
String url=req.getRequestURI().toString();
//是否包含login
if(url.contains("login")){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
String jwt=req.getHeader("token");//jwt默认封装在前端请求header的token标签中
//jwt是否为空
if(jwt==null||jwt.equals("")){
Result error = Result.error("NOT_LOGIN");
String notLogin= JSONObject.toJSONString(error);
res.getWriter().write(notLogin);
return;
}
//不为空则解析jwt
try{
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
Result error = Result.error("NOT_LOGIN");
String notLogin= JSONObject.toJSONString(error);
res.getWriter().write(notLogin);
return;
}
//jwt正确则放行
filterChain.doFilter(servletRequest,servletResponse);
}
}
资源访问后还会回到本方法,并执行放行之后的逻辑
多个过滤器会产生过滤器链,访问过滤器顺序按照过滤器名字的字母排序
**
Interceptor实现
**
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override//为true代表放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
//获取url
String url=req.getRequestURI().toString();
//是否包含login
if(url.contains("login")) return true;
String jwt=req.getHeader("token");
//jwt是否为空
if(jwt==null||jwt.equals("")){
Result error = Result.error("NOT_LOGIN");
String notLogin= JSONObject.toJSONString(error);
res.getWriter().write(notLogin);
return false;
}
//不为空则解析jwt
try{
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
Result error = Result.error("NOT_LOGIN");
String notLogin= JSONObject.toJSONString(error);
res.getWriter().write(notLogin);
return false;
}
return true;
}
}
@Configuration
public class Webconfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// /*代表一级路径 /**代表任意一级路径
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
**
- preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false
,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。 - postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller
中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。
Filter和Interceptor有什么区别?
**
Filter会拦截所有资源,而Interceptor只会拦截Spring相关资源
Filter先于Interceptor调用,Interceptor介于Servlet和Controller之间。
1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
在这里插入图片描述
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
在这里插入图片描述
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。