赞
踩
首先,我们要清楚我们使用的前后端交互协议是HTTP协议,而HTTP协议是无状态协议,所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。
这时候就需要会话跟踪了。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪技术有两种:
Cookie(客户端会话跟踪技术)
数据存储在客户端浏览器当中
Session(服务端会话跟踪技术)
数据存储在储在服务端
令牌技术
cookie 是客户端会话跟踪技术,它是将共享的数据存储在客户端浏览器的。
服务器会 自动 的将 cookie 响应给浏览器。
浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。
在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。
为什么这一切都是自动的?
是因为 cookie 它是 HTP 协议当中所支持的技术,而各大浏览器厂商都支持了这一标准。在 HTTP 协议官方给我们提供了一个响应头和请求头:
响应头 Set-Cookie :设置Cookie数据的
请求头 Cookie:携带Cookie数据的
优点:HTTP协议中支持的技术
缺点:移动APP中无法使用cookie
不安全,在本地存储,而且用户也可以手动禁止
cookie不能跨域
Session,它是服务器端会话跟踪技术,所以它是将共享数据存储在服务器端的。
浏览器第一次请求session,会话对象是不存在的,这个时候服务器会自动创建一个会话对象,每一个对象都会有一个ID。然后服务器端给浏览器响应数据的时候,会将session的ID通过cookie响应给浏览器。
接下来,在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就会从众多的 Session 当中来找到当前请求对应的会话对象Session 。
这样我们就可以通过 Session 会话对象在同一次会话的多次请求之间来共享数据了。
令牌其实就是给用户一个身份标识,由头部,荷载(搭载需要的业务数据),签名三部分组成。
通过令牌跟踪技术跟踪会话,在请求登录接口时,如果登录成功,我们可以生成一个令牌,这个令牌就是用户的合法身份凭证,我们响应数据的时候,就可以把令牌响应给前端。
而令牌可以把这个身份凭证存储在自己指定的地方,响应体/头都可以,接下来每次请求中都要将令牌携带到服务端,进行检验令牌的有效性。这样就可以共享数据了。
优缺点
优点:
支持PC端、移动端
解决集群环境下的认证问题
减轻服务器的存储压力(无需在服务器端存储)
缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)
首先需要在pom中引用依赖:
- <!-- JWT依赖-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
生成jwt令牌
- @Test
- public void genJwt(){
- Map<String,Object> 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() + 24*3600*1000)) //有效期
- .compact();
-
- System.out.println(jwt);
- }
解析令牌
- @Test
- public void parseJwt(){
- Claims claims = Jwts.parser()
- .setSigningKey("itheima")//指定签名密钥(必须保证和生成令牌时使用相同的签名密钥)
- .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk")
- .getBody();
-
- System.out.println(claims);
- }
而我们可以直接在工具类中使用
- public class JwtUtils {
-
- private static String signKey = "itheima";//签名密钥
- private static Long expire = 43200000L; //有效时间
-
- /**
- * 生成JWT令牌
- * @param claims JWT第二部分负载 payload 中存储的内容
- * @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)//指定令牌Token
- .getBody();
- return claims;
- }
- }
而我们使用了令牌之后,我们会发现,要想检验令牌的有效性,必须在每个接口都要进行校验,太复杂麻烦了,所以我们只需要拦截后,统一来进行校验,这里有两种方法:过滤器Filter和拦截器Interceptor。
就是使用过滤器来进行统一的令牌校验,只有通过了校验,才会放行,让它继续调用接口方法。
第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。
- @WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
- public class DemoFilter implements Filter {
- @Override //初始化方法, 只调用一次
- public void init(FilterConfig filterConfig) throws ServletException {
- System.out.println("init 初始化方法执行了");
- }
-
- @Override //拦截到请求之后调用, 调用多次
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- System.out.println("Demo 拦截到了请求...放行前逻辑");
- //放行
- chain.doFilter(request,response);
- }
-
- @Override //销毁方法, 只调用一次
- public void destroy() {
- System.out.println("destroy 销毁方法执行了");
- }
- }
而其中主要就是doFilter()方法,登录校验也主要在这个方法中。
- @Slf4j
- @WebFilter(urlPatterns = "/*") //拦截所有请求
- public class LoginCheckFilter implements Filter {
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
- //前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
-
- //1.获取请求url
- String url = request.getRequestURL().toString();
- log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login
-
-
- //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
- if(url.contains("/login")){
- chain.doFilter(request, response);//放行请求
- return;//结束当前方法的执行
- }
-
-
- //3.获取请求头中的令牌(token)
- String token = request.getHeader("token");
- log.info("从请求头中获取的令牌:{}",token);
-
-
- //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
- if(!StringUtils.hasLength(token)){
- log.info("Token不存在");
-
- Result responseResult = Result.error("NOT_LOGIN");
- //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
- String json = JSONObject.toJSONString(responseResult);
- response.setContentType("application/json;charset=utf-8");
- //响应
- response.getWriter().write(json);
-
- return;
- }
-
- //5.解析token,如果解析失败,返回错误结果(未登录)
- try {
- JwtUtils.parseJWT(token);
- }catch (Exception e){
- log.info("令牌解析失败!");
-
- Result responseResult = Result.error("NOT_LOGIN");
- //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
- String json = JSONObject.toJSONString(responseResult);
- response.setContentType("application/json;charset=utf-8");
- //响应
- response.getWriter().write(json);
-
- return;
- }
-
-
- //6.放行
- chain.doFilter(request, response);
-
- }
- }
其中我们用到了将对象转化成json字符串的工具类fastjson,需要我们引入依赖
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.76</version>
- </dependency>
是一种动态拦截方法调用的机制,类似于过滤器。
拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。
拦截器的作用:
拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
下面我们通过快速入门程序,来学习下拦截器的基本使用。拦截器的使用步骤和过滤器类似,也分为两步:
定义拦截器
注册配置拦截器
- //自定义拦截器
- @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 true; //true表示放行
- }
-
- //目标资源方法执行后执行
- @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 .... ");
- }
- }
-
- //注册配置拦截器
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
-
- //自定义的拦截器对象
- @Autowired
- private LoginCheckInterceptor loginCheckInterceptor;
-
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //注册自定义拦截器对象
- registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
- }
- }
其中:
preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
postHandle方法:目标资源方法执行后执行
afterCompletion方法:视图渲染完毕后执行,最后执行
拦截器/**必须两个** 不然只会拦截/emp 或者/dept 无法拦截/emp/1的多级路径。
最后,两种拦截方法都可以实现最终的验证,到底使用那种啦?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。