会话技术和过滤器

会话技术

1.Cookie

优点:http协议支持技术

缺点:移动端app无法使用cookie

           不安全,用户可以禁用cookie

           cookie不能跨域

2.session 

底层基于cookie

优点:存在服务器中安全

缺点:集群环境下重复登录

            cookie全部缺点

package com.itheima.controller;

import com.itheima.pojo.Result;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * HttpSession演示
 */
@Slf4j
@RestController
public class SessionController {

    //设置Cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response){
        response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
        return Result.success();
    }

    //获取Cookie
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if(cookie.getName().equals("login_username")){
                System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
            }
        }
        return Result.success();
    }



    //存值
    @GetMapping("/s1")
    public Result session1(HttpSession session){
        log.info("HttpSession-s1: {}", session.hashCode());

        session.setAttribute("loginUser", "tom"); //往session中存储数据
        return Result.success();
    }

    //取值
    @GetMapping("/s2")
    public Result session2(HttpSession session){
        log.info("HttpSession-s2: {}", session.hashCode());

        Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }
}

3.令牌

简洁自包含的格式,用于两方通信以json格式安全传输信息

组成

  1. Header:令牌类型,签名算法 如{"alg":"HS256","type":"JWT"}
  2. Payload(有效载荷)携带一些自定义数据,默认信息,如{"id":"1","username":"Tom"}
  3. Signature(签名) 负责Token被篡改,确保安全性,将header和palyoad融,并加入指定密钥,通过签名算法计算来的

加依赖jjwt

优点:支持pc,移动端

           解决集群环境下认证问题

           缓解服务器压力

缺点:要自己实现

过滤器

将资源请求拦截下来,实现特殊功能(登录校验,同一编码处理,敏感字符处理)

并且还有在启动类加

@ServletComponentScan //开启了SpringBoot对Servlet组件的支持
package com.itheima.filter;
import com.itheima.utils.CurrentHolder;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //1. 获取到请求路径
        String requestURI = request.getRequestURI(); // /employee/login

        //2. 判断是否是登录请求, 如果路径中包含 /login, 说明是登录操作, 放行
        if (requestURI.contains("/login")){
            log.info("登录请求, 放行");
            filterChain.doFilter(request, response);
            return;
        }

        //3. 获取请求头中的token
        String token = request.getHeader("token");

        //4. 判断token是否存在, 如果不存在, 说明用户没有登录, 返回错误信息(响应401状态码)
        if (token == null || token.isEmpty()){
            log.info("令牌为空, 响应401");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        //5. 如果token存在, 校验令牌, 如果校验失败 -> 返回错误信息(响应401状态码)
        try {
            Claims claims = JwtUtils.parseToken(token);
            Integer empId = Integer.valueOf(claims.get("id").toString());
            CurrentHolder.setCurrentId(empId); //存入
            log.info("当前登录员工ID: {}, 将其存入ThreadLocal", empId);
        } catch (Exception e) {
            log.info("令牌非法, 响应401");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        //6. 校验通过, 放行
        log.info("令牌合法, 放行");
        filterChain.doFilter(request, response);

        //7. 删除ThreadLocal中的数据
        CurrentHolder.remove();
    }
}

三个部分

环境释放,资源清理

@Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("init 初始化方法 ....");
    }

    //拦截到请求之后, 执行, 会执行多次
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("拦截到了请求.... 放行前 .... ");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);

        log.info("拦截到了请求.... 放行后 .... ");
    }

    //销毁方法, web服务器关闭的时候执行, 只执行一次
    @Override
    public void destroy() {
        log.info("destroy 销毁方法 ....");
    }

拦截路径

过滤器链

过滤器执行属性按照字母排序先后执行

拦截器

实现HandlerInterceptor

@preHandle资源方法运行前   返回值true 同意放行  

@postHandle资源方法运行后运行

@afterCompletion 视图渲染完毕后运行

配置还要WebMvcConfigurer  接口,负责拦截

拦截路径 

registry.addInterceptor(tokenInterceptor)
//                .addPathPatterns("/**") // 拦截所有请求
//                .excludePathPatterns("/login"); // 不拦截哪些请求

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(); // /employee/login
//
//        //2. 判断是否是登录请求, 如果路径中包含 /login, 说明是登录操作, 放行
//        if (requestURI.contains("/login")){
//            log.info("登录请求, 放行");
//            return true;
//        }

        //3. 获取请求头中的token
        String token = request.getHeader("token");

        //4. 判断token是否存在, 如果不存在, 说明用户没有登录, 返回错误信息(响应401状态码)
        if (token == null || token.isEmpty()){
            log.info("令牌为空, 响应401");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        //5. 如果token存在, 校验令牌, 如果校验失败 -> 返回错误信息(响应401状态码)
        try {
            JwtUtils.parseToken(token);
        } catch (Exception e) {
            log.info("令牌非法, 响应401");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        //6. 校验通过, 放行
        log.info("令牌合法, 放行");
        return true;
    }
}

package com.itheima.config;

import com.itheima.interceptor.DemoInterceptor;
import com.itheima.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 配置类
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

//    @Autowired
//    private DemoInterceptor demoInterceptor ;

    //@Autowired
    //private TokenInterceptor tokenInterceptor;

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

//        registry.addInterceptor(tokenInterceptor)
//                .addPathPatterns("/**") // 拦截所有请求
//                .excludePathPatterns("/login"); // 不拦截哪些请求
//    }
}

Filter与Interceptor区别

  1. 接口范围不同:过滤器实现Filter接口,拦截器实现HandlerInterceptor
  2. 拦截范围不同:过滤器会拦截所有资源,Interceptor只会拦截Spring环境的资源

AOP

面向切面编程,面向特定方法编程

应用通用逻辑(如日志、事务、安全)核心业务逻辑解耦

引入spring-boot-starter-aop

连接点:JoinPoint 被AOP控制的方法

通知:Advice:指重复逻辑,共性功能

切入点:PointCut,匹配连接点条件,通知只会在切入点方法执行时被应用

切面:Asperct,描述通知和切入点的对应关系

目标对象:target,通知的应用对象

@Aspect标识当前是一个AOP类

@Around("execution(*com.itheim.servicr.impl.*.*(..))")设置这个包下任意方法都执行

AOP执行流程

动态代理:代理对象

通知类型

@Around:环绕通知,此注解标注的通知方法在目标方法执行前,后都被执行

@Before:前置通知 此注解标注的通知方法在目标方法执行前被执行

@After:后置通知此注解标注的通知方法在目标方法执行后被执行

@AfterReturning:返回后通知,此注解标注的通知方法在目标方法执行后被执行,有异常不执行

@AfterThrowing:异常后通知,此注解标注的通知方法发生有异常执行

@pointCut

通知顺序

多个切面点匹配都到目标方法,目标方法运行时,多个通知方法都会执行

执行顺序

        默认按切面名执行

  1. 目标方法前的通知方法:字母排名靠前的先执行
  2. 目标方法后的通知方法:字母排名靠后的先执行

        用Order(数字)加载切面上控制顺序

  1.  目标方法前的通知方法:数字小的先执行
  2. 目标方法后的通知方法:数字小的后执行             

切入点表达式 execution

切入点表达式  @annotation

当在方法中加上@Log就会执行Aop

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
package com.itheima.aop;

import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.CurrentHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
@Aspect
@Component
public class  OperationLogAspect {

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 执行目标方法
        Object result = joinPoint.proceed();
        // 计算耗时
        long endTime = System.currentTimeMillis();
        long costTime = endTime - startTime;

        // 构建日志实体
        OperateLog olog = new OperateLog();
        olog.setOperateEmpId(getCurrentUserId()); // 这里需要你根据实际情况获取当前用户ID
        olog.setOperateTime(LocalDateTime.now());
        olog.setClassName(joinPoint.getTarget().getClass().getName());
        olog.setMethodName(joinPoint.getSignature().getName());
        olog.setMethodParams(Arrays.toString(joinPoint.getArgs()));
        olog.setReturnValue(result != null ? result.toString() : "void");
        olog.setCostTime(costTime);
        
        // 保存日志
        log.info("记录操作日志: {}", olog);
        operateLogMapper.insert(olog);

        return result;
    }

    private Integer getCurrentUserId() {
        return CurrentHolder.getCurrentId();
    }
}

连接点

如上代码

通过JoinPoint抽象连接点,可以获取他方法执行时的信息,如目标类名,方法,方法参数

  1. 对于@Around通知,只能用ProceedingJoinPoint 
  2. 对于其他4中只能用JoinPoint 

ThreadLocal

什么是ThreadLocal

是线程局部变量,有隔离性,不同线程不会干扰

ThreadLocal应用场景

同一个线程/同一个请求,数据共享,比如说记录日志,谁操作的,一个请求 利用aop来绑定操作方法,获取操作人id

package com.itheima.utils;

/**
 * 当前用户信息持有者工具类
 * 使用ThreadLocal存储当前线程的用户信息,确保线程安全
 */
public class CurrentHolder {

    // 存储当前用户ID的ThreadLocal变量
    private static final ThreadLocal<Integer> CURRENT_ID_LOCAL = new ThreadLocal<>();

    // 存储当前用户名的ThreadLocal变量
    private static final ThreadLocal<String> CURRENT_USERNAME_LOCAL = new ThreadLocal<>();

    private static final ThreadLocal<String> CURRENT_PASSWORD_LOCAL = new ThreadLocal<>();

    /**
     * 设置当前用户ID
     * @param employeeId 用户ID
     */
    public static void setCurrentId(Integer employeeId) {
        CURRENT_ID_LOCAL.set(employeeId);
    }

    /**
     * 获取当前用户ID
     * @return 用户ID
     */
    public static Integer getCurrentId() {
        return CURRENT_ID_LOCAL.get();
    }

    /**
     * 设置当前用户名
     * @param username 用户名
     */
    public static void setUsername(String username) {
        CURRENT_USERNAME_LOCAL.set(username);
    }

    /**
     * 获取当前用户名
     * @return 用户名
     */
    public static String getUsername() {
        return CURRENT_USERNAME_LOCAL.get();
    }


    public static void setPassword(String password) {
        CURRENT_PASSWORD_LOCAL.set( password);
    }

    public static String getPassword( String  password) {
        return CURRENT_PASSWORD_LOCAL.get();
    }




    /**
     * 清除当前线程存储的所有用户信息
     */
    public static void remove() {
        CURRENT_ID_LOCAL.remove();
        CURRENT_USERNAME_LOCAL.remove();
        CURRENT_PASSWORD_LOCAL.remove();
    }
}

配置文件的优先级

properties   yml  yaml

也可以通过java系统属性和命令行参数完成属性配置  命令行优先级高

Bean管理

bean作用域

bean默认单例   singleton  默认单例的bean在项目启动时,创建的,创建完毕后放入IOC容器

@Lazy延迟初始化,延迟到第一次使用的时候,创建bean

单例的bean用于没有修改数据的,不存在多线程共享数据的问题,减少bean的创建和销毁

多例的bean用于没有修改时间的,

面试题:Spring容器的bean是单列还是多例的,bean什么时候实例化

                默认单例,一般是启动的时候,可以通过@Lazy懒加载  (懒加载好处:加快启动速度,减少不必要资源浪费,解决依赖循环问题)

                Spring容器的bean是否线程安全

                bean的线程安全取决于bean的状态和bean的作用域

                单例bean:如果是无状态的bean,内部不保存任何状态,就是安全,有状态的bean,数据不同就有安全

第三方Bean

要管理第三方Bean,可以将这些bean集中管理,用@Configuration声明一个注解声明一个配置类

什么时候用@component声明bean,什么时候用@Bean注解

如果是项目中的自定义类用@component,第三方依赖的类用@Bean

SpringBoot原理

起步依赖     

  依赖传递

自动配置

什么是自动配置,

第三方依赖使用的@Component及衍生的注解声明@Bean不生效,需要被组件扫描注解扫描到

什么方案可以生效

第一种配置方案@ComponentScan组件扫描注解,,想要加入第三方依赖包,不仅要在pom文件加依赖,还有加扫描路径:太繁琐,性能不高

第二种@Import

导入普通类

导入配置类

导入ImportSelector接口实现类

第四种:通过@EnableXXX,封装@Import注解第三方依赖会自己配置好注解,

@Conditional及其衍生注解的作用是什么

满足给定条件,注册对于@Bean对象到IOC容器中

@Conditional及其衍生注解作用在什么地方

方法,类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值