HttpServletRequest增加消息头

本文介绍了一种在SpringCloudZUUL项目中使用自定义HttpServletRequest类——MutableHttpServletRequest,来解决请求转发和路由过程中的请求头信息丢失问题。通过继承HttpServletRequestWrapper并重写相关方法,实现了请求头的增加与获取功能。

由于在请求中请求域的属性在请求转发,路由等过程中,请求域的值会丢失,,在项目项目中使用请求头来传递信息,但是HttpRequest并没有实现增加请求头的方法,所以找到他的子类来实现

 
class MutableHttpServletRequest extends HttpServletRequestWrapper {
    // holds custom header and value mapping
    private final Map<String, String> customHeaders;
 
    public MutableHttpServletRequest(HttpServletRequest request){
        super(request);
        this.customHeaders = new HashMap<String, String>();
    }
    
    public void putHeader(String name, String value){
        this.customHeaders.put(name, value);
    }
 
    public String getHeader(String name) {
        // check the custom headers first
        String headerValue = customHeaders.get(name);
        
        if (headerValue != null){
            return headerValue;
        }
        // else return from into the original wrapped object
        return ((HttpServletRequest) getRequest()).getHeader(name);
    }
 
    public Enumeration<String> getHeaderNames() {
        // create a set of the custom header names
        Set<String> set = new HashSet<String>(customHeaders.keySet());
        
        // now add the headers from the wrapped request object
        @SuppressWarnings("unchecked")
        Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
        while (e.hasMoreElements()) {
            // add the names of the request headers into the list
            String n = e.nextElement();
            set.add(n);
        }
 
        // create an enumeration from the set and return
        return Collections.enumeration(set);
    }
}

使用:

public class SecurityFilter implements javax.servlet.Filter {
 
    @Override
    public void destroy() {
        
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(req);
        ...
        mutableRequest.putHeader("x-custom-header", "custom value");
        chain.doFilter(mutableRequest, response);
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }
}

但是项目中我使用的SpringCloud ZUUL中使用这样 的方式失败:

@Component
public class AccessFilter extends ZuulFilter {
    private  Logger log = LoggerFactory.getLogger(AccessFilter.class);

    @Autowired
    private VerificationHelper helper;

    private  BufferedReader reader=null;
    @Autowired
    private KeyAndFrequencyService service;

    @Autowired
    private ZuulTest zuulTest;

    @Autowired
    private PermissionHandler permissionHandler;




    @Override
    public String filterType() {
        //前置过滤器
        return "pre";
    }

    @Override
    public int filterOrder() {
        //优先级,数字越大,优先级越低
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        //是否执行该过滤器,true代表需要过滤
        return true;
    }


    @Override
    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();


        try {

            permissionHandler.setTokenExpireTime(200000000);
            String type = request.getHeader("type");
            zuulTest.say();
            System.out.println("......................................................");
            if (type == null) {

                System.out.println("......................................................验证1");

                Object object = helper.AccessZuul(request, ctx);
                return object;


            } else {

                System.out.println("......................................................验证2");





                PermissionResult result=permissionHandler.check(request);
                System.out.println(result);
                if(result.isState()){



          
MutableHttpServletRequest  mutRequest=new MutableHttpServletRequest (request);
                     //增加头部信息
                    DasAccountInfo accountInfo= permissionHandler.GetDasAccountInfoById(Integer.parseInt(result.getUserId()));
                    
                    mutRequest.putHeader("dasAccountInfo", JSON.toJSONString(disablePropertyName()))                   ;RequestContext.getCurrentContext().setRequest(mutRequest);


                    ctx.setSendZuulResponse(true);// 对该请求进行路由
                    ctx.setResponseStatusCode(200);
                    ctx.set("isSuccess", true);

                }else{
                    ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由
                    ctx.setResponseStatusCode(401);// 返回错误码
                    ctx.setResponseBody("{\"code\":0,\"result\":\"网关验证失败!验证方式为2\"}");// 返回错误内容
                    ctx.set("isSuccess", false);
                }

            }

        }catch (Exception e){
            e.printStackTrace();
            log.error("网关报错!!!",e.fillInStackTrace());
        }

           return null;
    }

使用zuul网关的自带的设置请求头的方法,在网关中设置的请求头可以被路由下面的服务获取到:

ctx.getZuulRequestHeaders().put("dasAccountInfo", JSON.toJSONString(disablePropertyName()));

参考:
https://blog.youkuaiyun.com/wangjia55/article/details/51712574/

package indi.backend.aspect; import indi.backend.annotation.Permissions; import indi.backend.exception.PermissionsException; import indi.backend.repository.dao.TokenPoolRepository; import indi.backend.repository.dao.UsersRepository; import indi.backend.repository.entity.TokenPool; import indi.backend.repository.entity.Users; import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; import java.util.UUID; @Aspect @Component public class PermissionsAspect { @Autowired private HttpServletRequest request; @Autowired private UsersRepository usersRepository; @Autowired private TokenPoolRepository tokenPoolRepository; @Before("@annotation(permissions)") public void before(JoinPoint joinPoint, Permissions permissions) { if (permissions.value() < 0 || permissions.value() > 3) { throw new PermissionsException("方法权限范围越界"); } if (permissions.value() != 0) { String token = request.getHeader("token"); if (token == null || token.isEmpty()) { throw new PermissionsException("传入的令牌为空"); } Users users = findUsers(UUID.fromString(token)); if (users.getPermissionsLevel() < permissions.value()) { throw new PermissionsException("用户权限不足"); } } } private Users findUsers(UUID token) { TokenPool tokenPool = tokenPoolRepository.findById(token).orElseThrow(() -> new PermissionsException("令牌不存在")); Date expiredTime = Date.from(LocalDateTime.now().plusSeconds(3600).atZone(ZoneId.systemDefault()).toInstant()); tokenPool.setExpiredTime(expiredTime); tokenPoolRepository.save(tokenPool); Users users = tokenPool.getUser(); if (!Boolean.TRUE.equals(users.getEnabled())) { throw new RuntimeException("账户【" + users.getAccount() + "】已被禁用"); } if (users.getLockStartTime() != null && users.getLockEndTime() != null) { if (users.getLockEndTime().getTime() > new Date().getTime()) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH时mm分"); throw new RuntimeException("账户【" + users.getAccount() + "】已被锁定,锁定时间为" + sdf.format(users.getLockStartTime()) + "至" + sdf.format(users.getLockEndTime())); } else { users.setLockStartTime(null); users.setLockEndTime(null); } } users.setLastActiveTime(expiredTime); usersRepository.save(users); return users; } } 优化代码
03-22
<think>嗯,用户让我优化这个名为PermissionsAspect的切面类代码。首先,我需要仔细阅读代码,理解它的功能。这个切面是用来处理权限验证的,使用了Spring AOP。主要逻辑是在方法执行前检查用户的权限级别,是否满足注解中指定的要求,同时处理令牌的有效性和用户状态。 首先,我会检查代码的结构和可能的改进点。注意到在@Before通知中,权限值的检查是硬编码的范围0到3,这可能会导致维护问题,如果权限级别范围变化,需要修改代码。或许可以将权限范围提取为常量或配置参数,提高灵活性。 然后,权限检查部分,如果权限值不是0,就进行令牌验证。这里可以拆分权限验证的逻辑,使其更模块化。比如将令牌验证和用户状态检查分成不同的方法,提高可读性。 在findUsers方法中,存在一些可能的问题。例如,使用了Date和LocalDateTime的转换,可能有时区处理的问题。另外,令牌的过期时间被设置为当前时间加3600秒,这个硬编码的数字应该提取为常量,方便后续调整。 还有,异常处理部分,抛出了RuntimeException,但最好使用具体的自定义异常,比如PermissionsException,保持异常体系的一致性。此外,用户状态的检查,如是否启用、是否被锁定,这些逻辑可以封装到单独的方法中,增强代码的可维护性。 注意到在更新用户最后活动时间时,使用了tokenPool的expiredTime,这可能不太直观,或许应该直接使用当前时间加上超时时间,或者另一个明确的变量。另外,SimpleDateFormat不是线程安全的,虽然在Spring的Bean中通常是单例的,但这里在方法内部创建可能没问题,不过可以考虑使用Java 8的DateTimeFormatter来替代。 此外,代码中可能存在重复的代码,比如处理用户锁定的时间,可以抽取成方法。还有,在更新tokenPool和users的时候,可能存在数据库操作,可以考虑添加事务管理,确保数据的一致性。 另外,findUsers方法的名字可能不太准确,因为它不仅查找用户,还执行了令牌的续期和用户状态的检查,或许可以重命名为更贴切的名字,如validateAndRefreshToken。 最后,代码的可测试性可能不足,依赖了HttpServletRequest、UsersRepository和TokenPoolRepository,可以考虑通过构造函数注入而不是字段注入,方便单元测试时模拟依赖。 总结下来,优化点包括:提取常量、拆分方法、使用自定义异常、改善日期处理、增强事务管理、提高代码可读性和可维护性,以及改进异常消息的提示信息。</think>我将分步骤为您分析如何优化该权限切面代码,并提出改进方案: 1. **常量提取与配置化** ```java // 权限范围常量 private static final int MIN_PERMISSION_LEVEL = 0; private static final int MAX_PERMISSION_LEVEL = 3; // Token有效期配置(单位:秒) private static final long TOKEN_EXPIRATION = 3600; ``` 2. **异常处理优化** ```java // 自定义异常统一处理 if (!Boolean.TRUE.equals(users.getEnabled())) { throw new PermissionsException("账户【" + users.getAccount() + "】已被禁用"); } ``` 3. **逻辑拆分** ```java @Before("@annotation(permissions)") public void before(JoinPoint joinPoint, Permissions permissions) { validatePermissionRange(permissions.value()); if (permissions.value() != MIN_PERMISSION_LEVEL) { validateTokenPermission(permissions.value()); } } private void validateTokenPermission(int requiredLevel) { String token = getValidToken(); Users user = validateUser(UUID.fromString(token)); checkPermissionLevel(user, requiredLevel); } ``` 4. **日期时间处理优化** ```java // 使用DateTimeFormatter替代SimpleDateFormat private static final DateTimeFormatter LOCK_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分"); ``` 5. **Token续期逻辑改进** ```java private void refreshTokenExpiration(TokenPool tokenPool) { LocalDateTime newExpiration = LocalDateTime.now().plusSeconds(TOKEN_EXPIRATION); tokenPool.setExpiredTime(Date.from(newExpiration.atZone(ZoneId.systemDefault()).toInstant())); tokenPoolRepository.save(tokenPool); } ``` 6. **用户状态校验封装** ```java private void validateUserStatus(Users user) { if (!Boolean.TRUE.equals(user.getEnabled())) { throw new PermissionsException("账户【" + user.getAccount() + "】已被禁用"); } if (user.getLockEndTime() != null && user.getLockEndTime().after(new Date())) { String lockedPeriod = formatLockPeriod(user); throw new PermissionsException("账户【" + user.getAccount() + "】已被锁定,锁定时间为" + lockedPeriod); } } ``` 7. **完整优化版本建议**: ```java @Aspect @Component public class PermissionsAspect { // 常量配置 private static final int MIN_PERMISSION = 0; private static final int MAX_PERMISSION = 3; private static final long TOKEN_EXPIRATION_SECONDS = 3600; private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分"); // 依赖注入... @Before("@annotation(permissions)") public void checkPermissions(JoinPoint joinPoint, Permissions permissions) { int requiredLevel = permissions.value(); validateLevelRange(requiredLevel); if (requiredLevel != MIN_PERMISSION) { Users user = validateAndGetUser(); checkPermissionLevel(user, requiredLevel); } } private Users validateAndGetUser() { String token = getValidTokenFromHeader(); TokenPool tokenPool = validateToken(token); refreshTokenExpiration(tokenPool); return validateUser(tokenPool.getUser()); } private String getValidTokenFromHeader() { String token = request.getHeader("token"); if (token == null || token.isBlank()) { throw new PermissionsException("请求头中缺少有效令牌"); } return token; } private TokenPool validateToken(String token) { return tokenPoolRepository.findById(UUID.fromString(token)) .orElseThrow(() -> new PermissionsException("无效的访问令牌")); } private void refreshTokenExpiration(TokenPool tokenPool) { LocalDateTime expiration = LocalDateTime.now().plusSeconds(TOKEN_EXPIRATION_SECONDS); tokenPool.setExpiredTime(toDate(expiration)); tokenPoolRepository.save(tokenPool); } private Users validateUser(Users user) { validateUserStatus(user); updateLastActiveTime(user); return user; } private void validateUserStatus(Users user) { if (!Boolean.TRUE.equals(user.getEnabled())) { throw new PermissionsException("账户【" + user.getAccount() + "】已被禁用"); } checkAccountLockStatus(user); } private void checkAccountLockStatus(Users user) { Date now = new Date(); if (user.getLockEndTime() != null && user.getLockEndTime().after(now)) { String lockPeriod = formatLockPeriod(user.getLockStartTime(), user.getLockEndTime()); throw new PermissionsException("账户【" + user.getAccount() + "】锁定中,锁定时间:" + lockPeriod); } clearExpiredLock(user, now); } private String formatLockPeriod(Date start, Date end) { return start.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() .format(TIME_FORMATTER) + " - " + end.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() .format(TIME_FORMATTER); } private void clearExpiredLock(Users user, Date now) { if (user.getLockEndTime() != null && user.getLockEndTime().before(now)) { user.setLockStartTime(null); user.setLockEndTime(null); usersRepository.save(user); } } private void updateLastActiveTime(Users user) { user.setLastActiveTime(toDate(LocalDateTime.now().plusSeconds(TOKEN_EXPIRATION_SECONDS))); usersRepository.save(user); } private Date toDate(LocalDateTime localDateTime) { return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } private void validateLevelRange(int level) { if (level < MIN_PERMISSION || level > MAX_PERMISSION) { throw new PermissionsException("请求权限级别越界,有效范围:" + MIN_PERMISSION + "-" + MAX_PERMISSION); } } private void checkPermissionLevel(Users user, int requiredLevel) { if (user.getPermissionsLevel() < requiredLevel) { throw new PermissionsException("权限不足,需要级别:" + requiredLevel + ",当前级别:" + user.getPermissionsLevel()); } } } ``` **主要优化点说明**: 1. **模块化拆分**:将大型方法拆分为多个单一职责的小方法 2. **异常统一处理**:全部使用PermissionsException保持异常体系一致性 3. **线程安全改进**:使用DateTimeFormatter替代SimpleDateFormat 4. **状态管理优化**:增加锁定状态自动清除逻辑 5. **时效性处理**:统一Token有效期和最后活跃时间的更新逻辑 6. **代码可读性**:通过方法命名提升代码自解释能力 7. **配置集中管理**:将魔法数值提取为常量 8. **校验逻辑增强**:增加令牌有效性校验和空令牌处理 9. **时间处理**:统一日期转换方法 10. **状态更新**:优化账户锁定状态的自动清理机制 建议后续可增加: 1. 令牌黑名单机制 2. 权限变更后的令牌失效机制 3. 动态Token有效期配置 4. 访问频率限制 5. 审计日志记录功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值