(1)统一拦截:可以使用两种技术实现,Filter过滤器 以及 Interceptor 拦截器。
(2)登录标记:就需要用户登录成功之后,每一次请求中,都可以获取到该标记。
(1)客户端会话跟踪技术:Cookie
(2)服务端会话跟踪技术:Session
这两个技术都可以实现会话跟踪,它们之间最大的区别:Cookie是存储在浏览器端,而Session是存储在服务器端
。
在现今前前后端分离开发模式下,Cookie、Session这种会话技术已很少使用,而且在服务器集群环境下 以及 客户端多样化的情况下,传统的Cookie、Session的会话方案就显得力不从心了,其主要问题,体现在两个方面:
(1)服务端集群环境下Session的共享问题。
(2)移动端APP端无法使用Cookie。
优势:
(1)解决了集群环境下的认证问题,减轻服务器端的存储压力
(2)支持PC端、移动端
(1)使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输
(2)不需要在服务器端保存相关信息
(3)jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等
(1)第一部分:Header(头),作用:记录令牌类型、签名算法等
{
"alg":"HS256",
"type":"JWT"
}
(2)第二部分:Payload(有效载荷),作用:携带一些用户信息及过期时间等
{
"id":"1",
"username":"Tom"
}
(3)第三部分:Signature(签名),作用:防止Token被篡改、确保安全性
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥
签名算法是一种用于验证数字签名的算法,它可以确保消息的完整性和真实性。签名算法通常用于数字证书和数字签名的验证,以确保通信双方之间的身份和消息的完整性。常见的签名算法包括RSA、DSA、ECDSA、EdDSA等。这些算法都使用数学原理来生成数字签名,以确保签名的可信度和唯一性。RSA算法是最常见的数字签名算法之一,它使用公钥加密和私钥解密来生成数字签名。DSA和ECDSA算法也被广泛使用,它们使用椭圆曲线密码学来生成数字签名。EdDSA算法是一种新的数字签名算法,它使用一种新的哈希函数和椭圆曲线密码学来生成数字签名。签名算法的安全性取决于使用的哈希函数和加密算法的安全性。因此,签名算法的安全性通常需要进行充分的评估和测试,以确保它们能够提供足够的安全性。
(1)在pom包中加入依赖
io.jsonwebtoken
jjwt
0.9.1
(2)生产JWT令牌代码
public class JwtDemo {
@Test
public void genJwt(){
Map claims = new HashMap<>();
claims.put("id",1);
claims.put("username","Tom");
String jwt = Jwts.builder()
.setClaims(claims) //执行第二部分负载, 存储的数据
.signWith(SignatureAlgorithm.HS256, "itheima") //签名算法及秘钥
.setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)) //设置令牌的有效期
.compact();
System.out.println(jwt);
}
}
(3)校验
@Test
public void parseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("itheima")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjU5OTk1NTE3LCJ1c2VybmFtZSI6IlRvbSJ9.EUTfeqPkGslekdKBezcWCe7a7xbcIIwB1MXlIccTMwo")
.getBody();
System.out.println(claims);
}
注意事项:
(1)JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的
(2)如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法
令牌一般在登陆时生成,登录成功之后,前端会在后面的每一次请求中将令牌携带过来,那接下来,我们需要做的就是需要在服务端统一拦截校验JWT令牌。
统一拦截请求,在服务端,我们可以通过两种手段实现:过滤器Filter、拦截器Interceptor
(创建filter包,在filter包里创建类定义过滤器)
(1)概念:Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一
(2)过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
(3)过滤器一般完成一些通用的操作,比如:登陆鉴权、统一编码处理、敏感字符处理等等…
(1)定义类,实现 Filter接口,并重写doFilter
方法
(2)配置Filter拦截资源的路径:在类上定义 @WebFilter 注解
(3)在doFilter方法中输出一句话,并放行
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
/**
*
* @param servletRequest 负责处理客户端发送过来的请求(获取参数)
* @param servletResponse 负责服务端响应数据给客户端
* @param filterChain 过滤链,实现是否进行拦截(截停,放行)
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException, ServletException {
System.out.println("拦截方法执行, 拦截到了请求 ...");
System.out.println("执行放行前逻辑 ...");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("执行放行后逻辑 ...");
}
}
4). 在引导类(运行的)上使用@ServletComponentScan
开启 Servlet 组件扫描
放行后访问对应资源,资源访问完成后,还会回到Filter中
如果回到Filter中,执行放行后的逻辑
4.Filter 拦截路径
拦截路径 |
urlPattern值 |
含义 |
拦截具体路径 |
/login |
只有访问 /login 路径时,才会被拦截 |
目录拦截 |
/emps/* |
访问/emps下的所有资源,都会被拦截 |
拦截所有 |
/* |
访问所有资源,都会被拦截 |
(1)获取请求url。
(2)判断请求url中是否包含login,如果包含,说明是登录操作,放行。
(3)获取请求头中的令牌(token)。
(4)判断令牌是否存在,如果不存在,返回错误结果(未登录)。
(5)解析token,如果解析失败,返回错误结果(未登录)。
(怎么把errorResult对象转换成JSON字符串)
//java对象 JSON对象之间的映射
ObjectMapper objectMapper = new ObjectMapper();
//把java对象转化为JSON字符串
String json = objectMapper.writeValueAsString(errorResult);
(6)放行。
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request , ServletResponse response , FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) request ;
HttpServletResponse response = (HttpServletResponse) response ;
//获取请求路径
String url = request.getRequestURL().toString();
//如果是login, 直接放行
if(url.contains("login")){
System.out.println("登录操作, 直接放行...");
filterChain.doFilter(req, res);
return;
}
//如果不是 login ,需要校验 token
String token = request.getHeader("token");
if(!StringUtils.hasLength(token)){ //如果没有JWT令牌
System.out.println("获取到token为空 , 返回错误信息...");
//返回 未登录 提示信息
String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(result);
return ;
}
//解析jwt令牌, 如果解析失败, 则说明令牌无效 , 返回 未登录 提示信息
try {
JwtUtils.parseJWT(token);
System.out.println("令牌解析成功, 直接放行 ...");
} catch (Exception e) {
e.printStackTrace();
System.out.println("令牌解析失败 , 返回错误信息...");
//返回 未登录 提示信息
String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(result);
return ;
}
//如果校验通过放行
filterChain.doFilter(req, res);
}
}
拦截器:(Interceptor)是一种动态拦截方法调用的机制,类似于过滤器。在SpringMVC中动态拦截控制器方法的执行
作用:在指定的方法调用前后执行预先设定的代码,完成功能增强
1.定义拦截器,实现HandlerInterceptor接口,并重写其所有方法。(主要是重写preHandle方法)
(先定义interceport包,在interceport包里面创建类定义拦截器)
(主方法上加上@Component 把它注入容器)
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行 , true : 放行 ; false : 不放行,拦截 ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle ....");
//如果校验通过放行
return false;
}
//目标资源方法执行后执行
@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 ....");
}
}
2.注册拦截器
(定义config包,在config包里创建类注册拦截器)
在主方法上加上@Configuretion 才能被识别到是配置类
(1)注入LoginCheckInterceptor(拦截器)
(2)重写addInterceptors方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
拦截路径 :
拦截路径 |
urlPattern值 |
含义 |
拦截具体路径 |
/login |
只有访问 /login 路径时,才会被拦截 |
目录拦截 |
/emps/* |
访问/emps下的下一级资源,如: /emps/1 ,但是 不会拦截 /emps/list/1,/emps/list/1/2 |
目录拦截 |
/emps/** |
访问/emps下的所有资源,都会被拦截 |
拦截所有 |
/** |
访问所有资源,都会被拦截 |
为了方便我们程序员找到问题,我们会加入日志@Slf4j
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* Exception异常分类
* - 运行时异常 : RuntimeException , 编译时无需处理 .
* - 编译时异常 : 非 RuntimeException , 编译时处理 .
*/
@ExceptionHandler(Exception.class)
public Result ex(Exception ex){
ex.printStackTrace();
log.error("全局异常处理",e)
return Result.error("系统繁忙, 请稍后重试 ... ");
}
}