Filter,Interceptor拦截器-登录校验
JWT令牌技术(JSON Web Token)
优缺点
优点:
支持PC端、移动端
解决集群环境下的认证问题
减轻服务器的存储压力(无需在服务器端存储)
缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)
JWT的组成: (JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{"alg":"HS256","type":"JWT"}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{"id":"1","username":"Tom"}
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。
JWT是如何将原始的JSON格式数据,转变为字符串的呢?
其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码
Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号
需要注意的是Base64是编码方式,而不是加密方式。
生成和校验
1). 首先我们先来实现JWT令牌的生成。要想使用JWT令牌,需要先引入JWT的依赖
<!-- JWT依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
在引入完JWT来赖后,就可以调用工具包中提供的API来完成JWT令牌的生成和校验。工具类:Jwts
2). 生成JWT代码实现:
@Test
public void testGenJwt() {Map<String, Object> claims = new HashMap<>();claims.put("id", 10);claims.put("username", "itheima");String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "aXRjYXN0").addClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)).compact();System.out.println(jwt);
}
3). 实现了JWT令牌的生成,下面我们接着使用Java代码来校验JWT令牌(解析生成的令牌):
JWT工具类:
在项目工程下创建 com.itheima.util
包,并把提供JWT工具类复制到该包下
package com.itheima.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;public class JwtUtils {private static String signKey = "SVRIRUlNQQ==";private static Long expire = 43200000L;/*** 生成JWT令牌* @return*/public static String generateJwt(Map<String,Object> claims){String jwt = Jwts.builder().addClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();return jwt;}/*** 解析JWT令牌* @param jwt JWT令牌* @return JWT第二部分负载 payload 中存储的内容*/public static Claims parseJWT(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();return claims;}
}
Filter拦截器
第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。
1). 定义过滤器
public class DemoFilter implements Filter {//初始化方法, web服务器启动, 创建Filter实例时调用, 只调用一次public void init(FilterConfig filterConfig) throws ServletException {System.out.println("init ...");}//拦截到请求时,调用该方法,可以调用多次public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {System.out.println("拦截到了请求...");}//销毁方法, web服务器关闭时调用, 只调用一次public void destroy() {System.out.println("destroy ... ");}
}
2). 配置过滤器
在定义完Filter之后,Filter其实并不会生效,还需要完成Filter的配置,Filter的配置非常简单,只需要在Filter类上添加一个注解:@WebFilter
,并指定属性urlPatterns
,通过这个属性指定过滤器要拦截哪些请求
当我们在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan
,通过这个@ServletComponentScan
注解来开启SpringBoot项目对于Servlet组件的支持。
Filter详解
过滤器的执行流程
在拦截到请求后,如果要继续访问,要执行放行操作。即doFilter()方法。在调用这个方法前的逻辑就是放行前的逻辑,在资源访问后,还会回答过滤器中执行放行后的逻辑。
过滤器当中我们拦截到了请求之后,如果希望继续访问后面的web资源,就要执行放行操作,放行就是调用 FilterChain对象当中的doFilter()方法,在调用doFilter()这个方法之前所编写的代码属于放行之前的逻辑。
在放行后访问完 web 资源之后还会回到过滤器当中,回到过滤器之后如有需求还可以执行放行之后的逻辑,放行之后的逻辑我们写在doFilter()这行代码之后。
过滤器的拦截路径配置
拦截路径 | urlPatterns值 | 含义 |
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
过滤器链
在一个web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链。
过滤器链上过滤器的执行顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。 比如:
AbcFilter
DemoFilter
这两个过滤器来说,AbcFilter 会先执行,DemoFilter会后执行
Interceptor拦截器
Interceptor拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。
下面我们通过快速入门程序,来学习下拦截器的基本使用。拦截器的使用步骤和过滤器类似,也分为两步:
定义拦截器
注册配置拦截器
1). 自定义拦截器
实现HandlerInterceptor接口,并重写其所有方法
//自定义拦截器
@Component
public class DemoInterceptor implements HandlerInterceptor {//目标资源方法执行前执行。 返回true:放行 返回false:不放行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle .... ");return true; //true表示放行}//目标资源方法执行后执行@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle ... ");}//视图渲染完毕后执行,最后执行@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion .... ");}
}
注意:
preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
postHandle方法:目标资源方法执行后执行
afterCompletion方法:视图渲染完毕后执行,最后执行
2). 注册配置拦截器
在 com.itheima
下创建一个包(config),然后创建一个配置类 WebConfig
, 实现 WebMvcConfigurer
接口,并重写 addInterceptors
方法
@Configuration
public class WebConfig implements WebMvcConfigurer {//自定义的拦截器对象@Autowiredprivate DemoInterceptor demoInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册自定义拦截器对象registry.addInterceptor(demoInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)}
}
拦截路径
excludePathPatterns("不拦截路径")
方法,指定哪些资源不需要拦截。
在拦截器中除了可以设置/**
拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
/* | 一级路径 | 能匹配/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 |
执行流程
如果过滤器和拦截器同时存在
过滤器时serlet提供的,拦截的范围广,能拦截所有请求。
拦截器是spring提供的,只能拦截spring中的请求,在controller调用前拦截