|
|
|
@ -0,0 +1,163 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* https://www.mall4j.com/
|
|
|
|
|
*
|
|
|
|
|
* 未经允许,不可做商业用途!
|
|
|
|
|
*
|
|
|
|
|
* 版权所有,侵权必究!
|
|
|
|
|
*/
|
|
|
|
|
package com.yami.shop.security.common.filter;
|
|
|
|
|
|
|
|
|
|
import cn.dev33.satoken.stp.StpUtil;
|
|
|
|
|
import cn.hutool.core.collection.CollectionUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import com.yami.shop.common.exception.YamiShopBindException;
|
|
|
|
|
import com.yami.shop.common.handler.HttpHandler;
|
|
|
|
|
import com.yami.shop.common.response.ResponseEnum;
|
|
|
|
|
import com.yami.shop.common.response.ServerResponseEntity;
|
|
|
|
|
import com.yami.shop.security.common.adapter.AuthConfigAdapter;
|
|
|
|
|
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
|
|
|
|
|
import com.yami.shop.security.common.manager.TokenStore;
|
|
|
|
|
import com.yami.shop.security.common.util.AuthUserContext;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
import org.springframework.util.AntPathMatcher;
|
|
|
|
|
|
|
|
|
|
import jakarta.servlet.Filter;
|
|
|
|
|
import jakarta.servlet.FilterChain;
|
|
|
|
|
import jakarta.servlet.FilterConfig;
|
|
|
|
|
import jakarta.servlet.ServletException;
|
|
|
|
|
import jakarta.servlet.ServletRequest;
|
|
|
|
|
import jakarta.servlet.ServletResponse;
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 授权过滤类,主要负责对请求进行授权相关的校验过滤操作。
|
|
|
|
|
* 只要实现AuthConfigAdapter接口,添加对应路径即可实现灵活的授权路径配置,以此来控制哪些请求需要授权,哪些不需要授权访问。
|
|
|
|
|
* 它在整个系统的安全机制中处于关键位置,是保障系统资源按权限访问的重要防线。
|
|
|
|
|
*
|
|
|
|
|
* @author 菠萝凤梨
|
|
|
|
|
* @date 2022/3/25 17:33
|
|
|
|
|
*/
|
|
|
|
|
@Component
|
|
|
|
|
public class AuthFilter implements Filter {
|
|
|
|
|
|
|
|
|
|
// 用于记录日志,方便在运行过程中输出相关的调试、错误等信息,便于排查问题
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
|
|
|
|
|
|
|
|
|
|
// 注入AuthConfigAdapter接口的实现类,通过它可以获取到配置的需要授权和不需要授权的路径信息
|
|
|
|
|
@Autowired
|
|
|
|
|
private AuthConfigAdapter authConfigAdapter;
|
|
|
|
|
|
|
|
|
|
// 注入HttpHandler,用于处理向Web端输出响应相关的操作,比如将服务器响应信息正确地返回给前端页面
|
|
|
|
|
@Autowired
|
|
|
|
|
private HttpHandler httpHandler;
|
|
|
|
|
|
|
|
|
|
// 注入TokenStore,主要用于管理和操作与令牌(Token)相关的存储及查询等功能,例如从存储中获取用户信息等
|
|
|
|
|
@Autowired
|
|
|
|
|
private TokenStore tokenStore;
|
|
|
|
|
|
|
|
|
|
// 通过配置文件注入令牌名称,用于后续从请求头中获取对应的令牌信息
|
|
|
|
|
@Value("${sa-token.token-name}")
|
|
|
|
|
private String tokenName;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void init(FilterConfig filterConfig) throws ServletException {
|
|
|
|
|
Filter.super.init(filterConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 核心的过滤方法,对每个经过该过滤器的请求进行处理,判断是否需要授权等操作。
|
|
|
|
|
*
|
|
|
|
|
* @param request 当前的Servlet请求对象,在这里被转换为HttpServletRequest以获取更多HTTP相关的请求信息
|
|
|
|
|
* @param response 当前的Servlet响应对象,在这里被转换为HttpServletResponse用于后续向客户端返回响应
|
|
|
|
|
* @param chain 过滤器链,用于将请求传递给下一个过滤器或者最终的目标资源(如Servlet、JSP页面等)
|
|
|
|
|
* @throws IOException 如果在处理请求或响应的输入输出流时出现错误则抛出此异常
|
|
|
|
|
* @throws ServletException 如果在Servlet处理过程中出现其他异常则抛出此异常
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
|
|
|
throws IOException, ServletException {
|
|
|
|
|
// 将ServletRequest转换为HttpServletRequest,方便获取HTTP请求相关的属性和信息,比如请求路径、请求头信息等
|
|
|
|
|
HttpServletRequest req = (HttpServletRequest) request;
|
|
|
|
|
// 将ServletResponse转换为HttpServletResponse,便于后续进行HTTP响应相关的操作,比如设置响应状态码、响应头、输出响应内容等
|
|
|
|
|
HttpServletResponse resp = (HttpServletResponse) response;
|
|
|
|
|
|
|
|
|
|
// 获取当前请求的URI(统一资源标识符),用于后续与配置的授权路径进行匹配判断
|
|
|
|
|
String requestUri = req.getRequestURI();
|
|
|
|
|
|
|
|
|
|
// 通过AuthConfigAdapter获取不需要授权的路径列表,用于判断当前请求是否命中这些无需授权的路径
|
|
|
|
|
List<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();
|
|
|
|
|
|
|
|
|
|
// 创建AntPathMatcher对象,用于进行路径的匹配操作,它支持通配符等方式来灵活匹配路径
|
|
|
|
|
AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
|
|
|
// 如果不需要授权的路径列表不为空,就遍历这些路径,检查当前请求的URI是否匹配其中某个无需授权的路径模式
|
|
|
|
|
if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
|
|
|
|
|
for (String excludePathPattern : excludePathPatterns) {
|
|
|
|
|
// 使用AntPathMatcher进行路径匹配,如果匹配成功,说明当前请求是不需要授权的,直接将请求传递给下一个过滤器或目标资源
|
|
|
|
|
if (pathMatcher.match(excludePathPattern, requestUri)) {
|
|
|
|
|
chain.doFilter(req, resp);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从请求头中获取名为tokenName的令牌信息,这个令牌通常用于验证用户的登录状态和权限等
|
|
|
|
|
String accessToken = req.getHeader(tokenName);
|
|
|
|
|
// 判断当前请求的URI是否匹配可能需要登录但不登录也能用的路径模式(通过AuthConfigAdapter中定义的常量路径进行匹配)
|
|
|
|
|
boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri);
|
|
|
|
|
|
|
|
|
|
// 用于存储从令牌中解析出来的用户信息,如果后续成功获取到用户信息则会赋值
|
|
|
|
|
UserInfoInTokenBO userInfoInToken = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 如果获取到的令牌信息不为空(即存在令牌),说明用户可能已经登录,需要进一步验证并获取用户信息
|
|
|
|
|
if (StrUtil.isNotBlank(accessToken)) {
|
|
|
|
|
// 使用StpUtil工具类来校验用户是否已经登录,若登录验证失败会抛出异常,在这里捕获异常并进行相应处理
|
|
|
|
|
try {
|
|
|
|
|
StpUtil.checkLogin();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 如果登录校验失败,通过HttpHandler将表示未授权的服务器响应信息输出到Web端(通常是返回给前端页面显示相应的错误提示)
|
|
|
|
|
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 如果登录校验通过,从TokenStore中根据令牌获取对应的用户信息(从缓存等存储中查询并解析用户信息)
|
|
|
|
|
userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true);
|
|
|
|
|
} else if (!mayAuth) {
|
|
|
|
|
// 如果没有令牌且当前请求也不属于可能不需要登录就能访问的路径,那么说明该请求需要授权但未提供有效令牌,返回表示未授权的响应给前端
|
|
|
|
|
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 将获取到的用户信息保存到AuthUserContext中,方便在后续的请求处理过程中(比如在其他地方需要获取当前登录用户信息时)可以直接获取
|
|
|
|
|
AuthUserContext.set(userInfoInToken);
|
|
|
|
|
|
|
|
|
|
// 如果前面的授权校验等操作都通过了,将请求传递给下一个过滤器或者最终的目标资源(如业务处理的Servlet等)继续处理
|
|
|
|
|
chain.doFilter(req, resp);
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 手动捕获非controller层抛出的异常,进行统一处理
|
|
|
|
|
if (e instanceof YamiShopBindException) {
|
|
|
|
|
// 如果是YamiShopBindException类型的异常,通过HttpHandler将该异常对应的响应信息输出到Web端
|
|
|
|
|
httpHandler.printServerResponseToWeb((YamiShopBindException) e);
|
|
|
|
|
} else {
|
|
|
|
|
// 如果是其他类型的异常,直接抛出,让上层的异常处理机制(比如容器的异常处理)去进一步处理
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
// 无论请求处理过程是否出现异常,最终都要清理AuthUserContext中的用户信息,避免数据残留影响下一次请求处理
|
|
|
|
|
AuthUserContext.clean();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void destroy() {
|
|
|
|
|
Filter.super.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|