登录认证-会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同意浏览器,以便在同一次会话的多次请求间共享数据。

会话跟踪方案:

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

Cookie

浏览器响应头 Set-Cookie:name=value

客户端请求头 Cookie:name=value

  • 优点:Http协议支持
  • 缺点:移动端App无法使用Cookie;不安全,客户可以自己禁用Cookie;不能跨域(跨域:客户端和服务端协议/ip/域名/端口不同都会跨域);【无状态】

Session

底层基于Cookie

优点:状态存储在服务端端的内存中,安全;【状态性,Session方案的核心优势在于“状态性”。服务器端有一个“指挥中心”,它知道哪些会话是活跃的、哪些是有效的。它可以随时吊销任何会话的访问权限。】

缺点:服务器集群环境下无法直接使用Session;Cookie的缺点

状态性就是,服务器有存储用户信息,用户只要一个token,无状态就是,用户自己存着信息,无论是否加密

令牌技术

优点:支持PC端和移动端;解决集群环境下的认证问题;减轻服务器端存储压力

1.解决扩展性,实现集群和分布式【多服务端】

使用令牌技术的服务器端不存储任何数据。传统Session存储在单个服务器的内存中,Session的旧解决方案:通过Session复制(性能开销大)或持久化到中央数据库(如Redis)(引入单点依赖和网络延迟)来缓解,但非根本解决。令牌的解决方案:采用无状态设计。服务器不存储会话数据,用户状态自包含在令牌中。任何服务器实例只需验证令牌有效性即可处理请求,天然支持分布式集群与水平扩展

2.解决架构耦合,实现多平台通用【多客户端】

Session通常与特定的服务器端技术栈(如Java Servlet、PHP、.NET)紧密耦合。它依赖于Cookie来传递Session ID,这限制了客户端的类型。

而令牌(尤其是JWT)是一个开放标准,任何语言、任何平台都能生成和验证。它不依赖Cookie,可以通过HTTP Header(如 Authorization: Bearer <token>)轻松传递,这使得它适用于:

原生手机App;API网关;不同域名的前端应用(解决跨域);第三方登录(OAuth 2.0的核心):你可以用一个令牌授权一个第三方应用访问你的部分资源,而无需提供用户名和密码

3.空间换时间,解决查询性能问题【快】

传统的session,即使使用Redis,每个请求都需要根据Session ID执行一次网络查询来获取用户数据。在高并发场景下,这会给中央存储带来巨大的压力和单点故障风险。而令牌技术:用户信息(如user_id, username)直接编码在令牌里,验证成功后即可直接使用,无需查询数据库。这用更大的网络传输量(令牌体积比Session ID大)换取了更低的服务器端延迟和负载

JWT不是一个简单的“加密Cookie”,而是一种更安全、更标准、更通用的无状态凭证机制

特性加密的Cookie(自研方案)JWT令牌(标准方案)
1. 安全核心保密性。依赖加密算法隐藏内容。如果密钥泄露,一切皆空。完整性与验证。依赖数字签名证明真伪。Payload可公开读取,但无法篡改。签名密钥泄露是灾难性的,但内容本身不依赖加密。
2. 标准化自定义的、私有的。格式和加解密方法由开发者自己定义。开放的、标准的。遵循RFC 7519标准,有固定的结构(Header.Payload.Signature),各语言都有现成库,互通性好。
3. 验证方式服务器必须解密整个Cookie才能验证其有效性。服务器只需验证签名(一个数学计算),而不需要先解密整个令牌。验证通过后,可直接解码读取Payload。
4. 内容可信度服务器解密后,直接信任其中的内容。服务器验证签名后,才信任其中的内容。这明确区分了“验证”和“信任”两个步骤。
5. 适用边界通常与Web域名绑定,通过Cookie机制管理。协议无关、域无关。可用于任何类型的客户端(App、桌面应用)、任何API,不受同源策略限制。

过滤器与拦截器

1. 过滤器示例:记录请求耗时和字符编码

// 使用 @WebFilter 注解或通过 FilterRegistrationBean 配置
@Component
public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        long startTime = System.currentTimeMillis();
        
        // 1. 前置处理:设置编码
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        System.out.println("Filter: 请求URI - " + httpRequest.getRequestURI());
        
        // 2. 放行
        chain.doFilter(request, response);
        
        // 3. 后置处理:记录耗时
        long endTime = System.currentTimeMillis();
        System.out.println("Filter: 请求处理耗时 - " + (endTime - startTime) + "ms");
    }
}

2. 拦截器示例:登录验证

@Component
public class AuthInterceptor implements HandlerInterceptor {

    // 可以直接注入Spring管理的Bean
    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在Controller执行之前
        String token = request.getHeader("Authorization");
        if (token != null && userService.validateToken(token)) {
            // 验证通过,放行
            return true;
        } else {
            // 验证失败,直接返回401未授权,不再执行Controller
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在Controller执行之后,视图渲染之前
        // 可以对ModelAndView进行修改
        System.out.println("Interceptor: Controller执行完毕");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求结束之后,视图渲染完毕之后
        // 常用于资源清理、异常日志记录等
        if (ex != null) {
            System.err.println("Interceptor: 请求处理发生异常 - " + ex.getMessage());
        }
    }
}

// 配置拦截器,需要实现 WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**") // 拦截/api开头的路径
                .excludePathPatterns("/api/login"); // 排除登录接口
    }
}

事务管理

事务回滚

@Transaction

默认情况下,只有出现RuntimeException才回滚异常,rollbackFor属性用于控制出现何种异常时回滚事务。

@Transaction(rollbackFor = Exception.class)
@Override
public void delete(Integer id) throws Exception {
    deptMapper.deleteById(id);
    if (true) throw new Exception("error!");
    empMapper.deleteByDeptId(id);
}

事务属性-传播行为

根据propagation属性确定传播行为

@Transactional(propagation = Propagation.REQUIRED)

REQUIRED:(默认)需要事务,有则加入,无则创建新事务

REQUIRES_NEW:需要新事务,无论有无,总是创建新事务

eg. 需求:解散部门时,无论是成功还是失败,都要记录操作日志。
步骤:
①.解散部门:删除部门、删除部门下的员工
②.记录日志到数据库表中

@Transactional
@Override
public void delete(Integer id) {
    try {
        deptMapper.deleteById(id); //删除部门
        int i = 1/0;
        empMapper.deleteByDeptId(id); //删除部门下的员工
    } finally {
        DeptLog log = new DeptLog();
        log.setCreateTime(LocalDateTime.now());
        log.setDescription("执行了解散部门操作,此次解散的是"+id+"号部门");
        deptLogService.insert(log); //记录操作日志
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
    deptLogMapper.insert(deptLog);
}

这个insert方法如果不设置为Propagation.REQUIRES_NEW,在delete失败时执行finally时事务回滚(insert事务也加入了delete事务),导致insert也被回滚,导致记录日志失败。

  • REQUIRED:大部分情况下都是用该传播行为即可。
  • REQUIRES_NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
分类: Java-Backend 标签: Java

评论

暂无评论数据

暂无评论数据

目录