当前位置: 首页 > news >正文

JavaWeb25.02.27

JavaWeb学习记录

2025.02.27 学习小总结

今天主要看的是登录验证这个板块,认识了三个登录会话技术,两个登录校验的方法,以及SpringAOP的入门案例。

登录校验

学习的三个会话技术分别是:Cookie,Session和令牌技术,主要使用的还是令牌技术,另外两个技术大概了解了一下,这里就不赘述了。

学习的令牌技术是JWT令牌技术,目前应用的比较广泛,JWT令牌由三个部分组成

  1. 头部:(typ:标识令牌的类型 ,alg:使用的签名算法)

  2. 有效载荷 :有效载荷包含了声明,声明是关于实体以及其他数据的声明,写的案例是关于校验登陆的案例,所以令牌里面携带的是id(唯一值)和用户名

  3. 签名:为了防止数据被篡改,JWT需要使用密钥进行签名,密钥是由服务器端自己提供的

下面的代码就是关于JWT的使用:生成JWT令牌和解析JWT令牌,里面还可以添加上过期时间

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;
    }
}

JWT令牌会储存在每一个请求的token里面,会一直携带着,可以根据这个特点来进行登陆校验的功能。但是因为一个案例中的控制层和服务层的方法有很多。不可能在每一个方法中都添加上校验,这样特别的麻烦,于是就有了过滤器和拦截器。

过滤器可以拦截所有的请求,浏览器发送请求的时候回先到达过滤器,如果通过验证,过滤器就放行,再到控制层,服务层和数据持久层。然后返回的时候,也会经过过滤器,再返回到浏览器。所以可以在过滤器中解析令牌,判断该请求是否合法。

下面是代码示例:

@Slf4j
@WebFilter("/*")
public class TokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.首先将协议转化为HTTP类型的协议
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //2.获取请求路径
        String requestURI = request.getRequestURI();

        //3.判断路径中是否包含登录请求,如果是就放行
        if(requestURI.contains("login")){
            filterChain.doFilter(request,response);
            return;
        }

        //4.获取请求头,判断是否为空,为空就响应401状态码
        String token = request.getHeader("token");
        if(token==null ||token.isEmpty()){
            log.info("令牌为空,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        //5.解析请求头token
        try {
            Claims claims = JwtUtils.parseJWT(token);
            Integer empId=Integer.valueOf(claims.get("id").toString());
            CurrentHolder.setCurrentId(empId);
            log.info("解析成功,放行");
            filterChain.doFilter(request,response);
        } catch (Exception e) {
            log.info("令牌解析失败,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        CurrentHolder.remove();
    }
}

需要拦截的路径在注解@WebFilter中添加 /* 就是拦截所有的路径
同时在启动Spring方法上还要添加注解@ServletComponentScan,这样能够扫描到所有的拦截。过滤器的顺序是按照自然排序的。
拦截器也差不多是一个道理,在过滤器后面,是先到过滤器再到拦截器,再到控制层然后往下。但拦截器只能拦截Spring的请求。

下面是拦截器的代码:

这是注册拦截器的代码:

package com.itheima.interceptor;

import com.itheima.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求路径
        String requestURI = request.getRequestURI();

        //2.判断路径中是否包含登录请求,如果是就放行
        if(requestURI.contains("login")){
            return true;
        }

        //4.获取请求头,判断是否为空,为空就响应401状态码
        String token = request.getHeader("token");
        if(token==null ||token.isEmpty()){
            log.info("令牌为空,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        //5.解析请求头token
        try {
            JwtUtils.parseJWT(token);
            return true;
        } catch (Exception e) {
            log.info("令牌解析失败,响应401状态码");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
    }
}

还需要实现WebMvcConfigure接口 启动拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器
     */
    @Autowired
    private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
}

这样就能够顺利的完成登录校验的功能了

SpringAOP

SpringAOP解决了一些重复代码的问题。例如日志记录,比如要记录对员工表的操作,添加,删除,更新员工表的时间,案例里这样的表和同样的操作有很多,如果一个一个写,记录操作前时间,操作后时间,操作时间,代码重复很高,而且也很麻烦,这个时候就有AOP技术,使用代理模式的方法,在目标资源执行前执行,在目标资源执行后执行,资源就省略了一大部分的代码。
下面是代码示例:

@Aspect
@Component
public class OperationLogAspect {

    @Autowired
    private OperateLogMapper operateLogMapper;

    // 环绕通知
    @Around("@annotation(com.itheima.anno.LogOperation)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        // 执行方法
        Object result = joinPoint.proceed();
        // 当前时间
        long endTime = System.currentTimeMillis();
        // 耗时
        long costTime = endTime - startTime;

        // 构建日志对象
        OperateLog operateLog = new OperateLog();
        operateLog.setOperateEmpId(getCurrentUserId()); // 需要实现 getCurrentUserId 方法
        operateLog.setOperateTime(LocalDateTime.now());
        operateLog.setClassName(joinPoint.getTarget().getClass().getName());
        operateLog.setMethodName(joinPoint.getSignature().getName());
        operateLog.setMethodParams(Arrays.toString(joinPoint.getArgs()));
        operateLog.setReturnValue(result.toString());
        operateLog.setCostTime(costTime);

        // 插入日志
        operateLogMapper.insert(operateLog);
        return result;
    }
    // 示例方法,获取当前用户ID
    private int getCurrentUserId() {
        return CurrentHolder.getCurrentId();
    }
}

这一句代码,就是执行目标资源的代码
Object result = joinPoint.proceed();

这是指定生效的目标资源,annotion是采用了注解的方式,在需要执行这个放的方法名上加上对应的注释,就可以生效了

@Around("@annotation(com.itheima.anno.LogOperation)")

自定义的注解如下:target注解是使方法生效 retention是在运行时生效

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
}

整个案例需要完成的是记录员工的操作日志,所以在执行每一个方法的时候需要知道员工的名字,这个时候就要想什么是一直携带着这个信息的。令牌技术里的token,就记录了员工的id,所以我们可以解析令牌来获取员工的id,然后记录到日志里面。这个时候就有了ThreadLocal,这是一个线程的存储空间。

每一次请求都是一个单独的线程,所以资源互不影响,创建一个关于ThreadLocal工具类的方法,来设置和获取id,在最后执行完的时候还要销毁id,避免资源空间的浪费。

public class CurrentHolder {

    private static final ThreadLocal<Integer> CURRENT_LOCAL = new ThreadLocal<>();

    public static void setCurrentId(Integer employeeId) {
        CURRENT_LOCAL.set(employeeId);
    }

    public static Integer getCurrentId() {
        return CURRENT_LOCAL.get();
    }

    public static void remove() {
        CURRENT_LOCAL.remove();
    }
}

在这里插入图片描述
在这里插入图片描述

这样登录校验功能和日志记录功能就顺利完成了!

题外话:

希望自己能够坚持记录和总结下学习的内容(虽然有点潦草),但希望自己每天的学习是有收获的,不能因为着急学习技术,就囫囵吞枣!希望能尽快学好,找到实习吧!

相关文章:

  • 【前端】XML,XPATH,与HTML的关系
  • 推荐算法工程师的技术图谱和学习路径
  • 实验环境搭建集锦(docker linux ros2+强化学习环境+linux上单片机串口调试)
  • Kylin麒麟操作系统服务部署 | Nginx服务部署
  • BRD4缺失通过GRP78灭活内质网应激,延缓脱氢表雄酮诱导的卵巢颗粒细胞凋亡
  • 波导阵列天线 学习笔记11双极化全金属垂直公共馈电平板波导槽阵列天线
  • 【技术笔记】Cadence 实现原理图元器件的自动标号
  • [教程]在CentStream 9简单部署一个Nginx web服务器
  • 基于多层感知机(MLP)实现MNIST手写体识别
  • SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
  • [高等数学] 定积分的概念与性质
  • 《从入门到精通:蓝桥杯编程大赛知识点全攻略》(十八)-农夫约翰的奶酪块、蛋糕游戏、奶牛体检
  • linux 后台执行并输出日志
  • Filp动画
  • 大数据SQL调优专题——底层调优
  • OpenCV(11):人脸检测、物体识别
  • Ubuntu本地使用AnythingLLM
  • 网络通信/IP网络划分/子网掩码的概念和使用
  • mysql的主从同步
  • Java CAS 与 AQS
  • 南山品牌网站建设企业/爱站网长尾关键词挖掘工具
  • 专业的销售网站/互联网公司
  • 网站建设设计哪家好/seo站外推广有哪些
  • 西安市高新区建设规划局网站/免费二级域名分发
  • 宁波seo服务推广平台/山东网络优化公司排名
  • 自建站和独立站/爱站网官网