登录功能
对于没有登录管理系统的用户,这些用户是不可以直接对其它的资源进行访问的。但是目前我们并没有做到要对用户进行拦截的操作。总共有这几种方法
jwt和token验证+Filter 过滤
jwt和Interceptor拦截器拦截
JWT令牌
JWT令牌就是一个经过长编码后的一个字符串,其内容主要可以分为
我们可以使用JWTS工具来生成一个jwt令牌
1
2
3
4
5
6
|
String jwt = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expireTime))
.compact();
return jwt;
|
其中,Claims是指定要进行填充的字段,其类型是Map<String, Object>数据类型,第二个参数就是去指定签名算法和签名的key。第三个参数就是去设置这个令牌的有效时间,最后把他们打包为一个字符串即可。
同理,我们也可以解析一个JWT令牌
1
2
3
4
5
6
7
|
public static Claims parseJwt(String jwt) {
Claims body = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return body;
}
|
只要指定好这个生成JWT时对应的签名,那么就可以得到这个JWT令牌,并且,如果这个JWT已经过期或者签名被篡改,那么在解析的过程中就会引发异常,因此可以在校验JWT是否合法中起到作用,对应的,如果在校验过程中引发异常,那么就可能是下面这几种情况
可以用来进行对用户的校验。
Filter过滤器
可以使用过滤器来过滤请求。
实现Filter接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@WebFilter(urlPatterns = "/login") // 拦截器需要拦截的路径
public class DemoFilter implements Filter {
// 初始化方法,只调用一次
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter开始调用了");
}
// 拦截时调用该方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("放行前的逻辑");
// 放行逻辑
// 如果不写下面这行放行逻辑的代码,那么数据其实就一直处于被拦截的状态
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("放行后的逻辑");
}
// 销毁方法,只执行一次
@Override
public void destroy() {
Filter.super.destroy();
}
}
|
这里总共是需要重写三个方法,其中的doFilter对应我们的拦截逻辑。除此之外,我们还需要加上一个@WebFilter接口,里面可以指定要拦截的url。
实现登录验证
使用Filter来进行拦截,一种可行的思路是:无论是哪种方法,我们都进行拦截,只不过在拦截时进行区分。
-
如果当前方法是login方法,那么我们直接放行,让用户登录即可。
-
如果是非login方法,我们需要对用户进行JWT令牌校验
- 首先
JWT令牌在登录后服务器会返回给前端浏览器
- 浏览器每次请求都会在
header里面携带这个JWT令牌
如果在验证过程中发生异常,那么说明JWT令牌存在问题,我们不能让该请求通过
-
只有JWT验证成功时才可以登录。
将拦截器的操作全部封装在doFilter里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取请求的url
String urlRequest = request.getRequestURL().toString();
if (urlRequest.contains("login")) {
// 直接放行
filterChain.doFilter(request, response);
return;
}
// 此时拦截到的就是非login请求
// 获取token
String token = request.getHeader("token");
// 使用jwt对token进行校验
log.info("token:{}", token);
if (!StringUtils.hasLength(token)) {
log.info("token不存在");
// 将这个信息返回回去
Result notLogin = Result.error("NOT_LOGIN");
String json = JSONObject.toJSONString(notLogin);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
// 令牌存在
// 将令牌进行解析
try {
JwtUtil.parseJwt(token);
} catch (Exception e) {
log.info("token解析失败");
Result notLogin = Result.error("NOT_LOGIN");
String json = JSONObject.toJSONString(notLogin);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
}
// 如果不发生错误,那么就在这里放行
filterChain.doFilter(request, response);
}
|
所以其拦截思路是:
- 拦截所有的请求,
login操作除外
- 拦截到请求后,检验
JWT,JWT检验无误后放行
Interceptor拦截
需要实现HandlerInterceptor接口,同时重写这个函数里面的方法。
1
2
3
4
5
|
preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
postHandle方法:目标资源方法执行后执行
afterCompletion方法:视图渲染完毕后执行,最后执行
|
这里我们只需要重写preHandle方法即可,注意方法返回true代表是放行操作,返回false就是不放行。
demo
逻辑几乎相同,不过在不放行时选择返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 返回值为True表示放行
// 返回值为False表示不放行
// 实现登录拦截
// 获取请求的url
log.info("Interceptor....");
String urlRequest = request.getRequestURL().toString();
if (urlRequest.contains("login")) {
// 直接放行
return true;
}
// 其它操作
String token = request.getHeader("token");
if (!StringUtils.hasLength(token)) {
log.info("token不存在");
// 将这个信息返回回去
Result notLogin = Result.error("NOT_LOGIN");
String json = JSONObject.toJSONString(notLogin);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);// token不存在
return false;
}
// 检验token
try {
JwtUtil.parseJwt(token);
}catch (Exception e){
log.info("token不存在");
// 将这个信息返回回去
Result notLogin = Result.error("NOT_LOGIN");
String json = JSONObject.toJSONString(notLogin);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;
}
return true;
}
|
注册WebConfig
1
2
3
4
5
|
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
|
添加路径
在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:
| 拦截路径 |
含义 |
举例 |
| /* |
一级路径 |
能匹配/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 |
以上就是拦截器的执行流程。通过执行流程分析,大家应该已经清楚了过滤器和拦截器之间的区别,其实它们之间的区别主要是两点:
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
- 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
总结
我们使用两种方式实现了登录校验功能,一般来说,对于api之类的请求,我们可以直接使用Interceptor来实现~