系统白名单接口添加自定义验证(模仿oauth2.0),防安全扫描不通过

1.获取临时code

bash 复制代码
/**
     * 获取临时code
     */
    @GetMapping("/getCode")
    public ActionResult<String> getCode() {

        String randomCode = RandomUtil.randomNumbers(6);
        // 300秒
        redisUtil.insert(VALIDCODE + randomCode, randomCode, 300);
       return ActionResult.success("success",randomCode);

    }

2.获取token

2.1 请求参数

bash 复制代码
import lombok.Data;

@Data
public class StaticTokenDTO {

    // 临时code
    private Integer code;

    // 自定义的appID
    private String appId;

    // 自定义的秘钥
    private String appSecret;

}

2.2配置的应用凭证

bash 复制代码
// 配置的应用凭证 实际项目中应该从数据库或配置文件中获取
    private static final Map<String, String> APP_CREDENTIALS = new HashMap<>();
    static {
        APP_CREDENTIALS.put("remote001","MIGfMA0GCSqGSIb3");
    }

2.3 获取静态token

bash 复制代码
private static final String VALIDCODE = "validcode:";
    private static final String ACCESS_TOKEN_PREFIX = "access_token:";
    private static final long ACCESS_TOKEN_EXPIRE = 7200; // 2小时过期
    private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
    private static final long REFRESH_TOKEN_EXPIRE = 604800; // 7天过期


/**
     * 获取静态token
     */
    @PostMapping("/getStaticToken")
    public ActionResult<Map<String, Object>> getStaticToken(StaticTokenDTO staticToken) {
        try {
            // 1. 参数验证
            if (staticToken == null) {
                return ActionResult.fail("参数不能为空");
            }

            if (ObjectUtil.isEmpty(staticToken.getCode()) || ObjectUtil.isEmpty(redisUtil.getString(VALIDCODE + staticToken.getCode()))) {
                return ActionResult.fail("code 验证失败");
            }

            if(StringUtils.isBlank(staticToken.getAppId()) || StringUtils.isBlank(staticToken.getAppSecret())){
                return ActionResult.fail("appId 和 appSecret 不能为空");
            }
            
            // 2. 验证应用凭证
            String expectedSecret = APP_CREDENTIALS.get(staticToken.getAppId());
            if (expectedSecret == null) {
                return ActionResult.fail("无效的 appId");
            }
            
            if (!expectedSecret.equals(staticToken.getAppSecret())) {
                return ActionResult.fail("appSecret 验证失败");
            }

            // 删除 验证码
            redisUtil.remove(VALIDCODE + staticToken.getCode());
            
            // 3. 生成安全的 accessToken
            String accessToken = generateSecureAccessToken(staticToken);
            
            // 4. 将 token 存储到 Redis 中,设置过期时间
            String tokenKey = ACCESS_TOKEN_PREFIX + accessToken;
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("appId", staticToken.getAppId());
            tokenInfo.put("createTime", System.currentTimeMillis());
            tokenInfo.put("expireTime", System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE * 1000);
            
            redisUtil.insert(tokenKey, JsonUtil.getObjectToString(tokenInfo), ACCESS_TOKEN_EXPIRE);
            
            // 4.1 生成并存储 refreshToken(与 accessToken 绑定)
            String refreshToken = RandomUtil.randomString(32);
            String refreshTokenKey = REFRESH_TOKEN_PREFIX + refreshToken;
            long refreshExpire = REFRESH_TOKEN_EXPIRE;
            Map<String, Object> refreshInfo = new HashMap<>();
            refreshInfo.put("appId", staticToken.getAppId());
            refreshInfo.put("bindAccessToken", accessToken);
            refreshInfo.put("createTime", System.currentTimeMillis());
            refreshInfo.put("expireTime", System.currentTimeMillis() + refreshExpire * 1000);
            redisUtil.insert(refreshTokenKey, JsonUtil.getObjectToString(refreshInfo), refreshExpire);
            
            // 5. 返回结果
            Map<String, Object> result = new HashMap<>();
            result.put("accessToken", accessToken);
            result.put("refreshToken", refreshToken);
            result.put("expiresIn", ACCESS_TOKEN_EXPIRE);
            result.put("tokenType", "Bearer");
            
            return ActionResult.success(result);
            
        } catch (Exception e) {
            return ActionResult.fail("生成 token 失败: " + e.getMessage());
        }
    }

3.刷新 token 调用刷新token方法后,以前的accessToken 被失效

bash 复制代码
@PostMapping("/refreshToken")
    public ActionResult<Map<String, Object>> refreshToken(String refreshToken) {
        try {
            // 校验 refreshToken 是否有效
            if (StringUtils.isBlank(refreshToken)) {
                return ActionResult.fail("无效的 refreshToken");
            }

            String refreshKey = REFRESH_TOKEN_PREFIX + refreshToken;
            String refreshInfoStr = (String) redisUtil.getString(refreshKey);
            if (StringUtils.isBlank(refreshInfoStr)) {
                return ActionResult.fail("无效的 refreshToken");
            }

            Map<String, Object> refreshInfo = JsonUtil.stringToMap(refreshInfoStr);
            String appId = String.valueOf(refreshInfo.get("appId"));
            String bindAccessToken = String.valueOf(refreshInfo.get("bindAccessToken"));

            // 尝试解析旧 accessToken 获取 code
            Integer code = null;
            try {
                String jwtSecret = getJwtSecret();
                Map<String, Object> claims = Jwts.parser()
                        .setSigningKey(jwtSecret)
                        .parseClaimsJws(bindAccessToken)
                        .getBody();
                Object codeObj = claims.get("code");
                if (codeObj instanceof Integer) {
                    code = (Integer) codeObj;
                } else if (codeObj instanceof Number) {
                    code = ((Number) codeObj).intValue();
                }
            } catch (Exception ignore) {
            }

            // 失效旧的 accessToken 和 refreshToken
            if (StringUtils.isNotBlank(bindAccessToken)) {
                redisUtil.remove(ACCESS_TOKEN_PREFIX + bindAccessToken);
            }
            redisUtil.remove(refreshKey);

            // 生成新的 accessToken
            StaticTokenDTO staticTokenDTO = new StaticTokenDTO();
            staticTokenDTO.setAppId(appId);
            staticTokenDTO.setAppSecret(APP_CREDENTIALS.get(appId));
            staticTokenDTO.setCode(code);
            String newAccessToken = generateSecureAccessToken(staticTokenDTO);

            // 存储新的 accessToken
            String newAccessTokenKey = ACCESS_TOKEN_PREFIX + newAccessToken;
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("appId", appId);
            tokenInfo.put("createTime", System.currentTimeMillis());
            tokenInfo.put("expireTime", System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE * 1000);
            redisUtil.insert(newAccessTokenKey, JsonUtil.getObjectToString(tokenInfo), ACCESS_TOKEN_EXPIRE);

            // 生成并存储新的 refreshToken(与新的 accessToken 绑定)
            String newRefreshToken = RandomUtil.randomString(32);
            String newRefreshTokenKey = REFRESH_TOKEN_PREFIX + newRefreshToken;
            long newRefreshExpire = REFRESH_TOKEN_EXPIRE;
            Map<String, Object> newRefreshInfo = new HashMap<>();
            newRefreshInfo.put("appId", appId);
            newRefreshInfo.put("bindAccessToken", newAccessToken);
            newRefreshInfo.put("createTime", System.currentTimeMillis());
            newRefreshInfo.put("expireTime", System.currentTimeMillis() + newRefreshExpire * 1000);
            redisUtil.insert(newRefreshTokenKey, JsonUtil.getObjectToString(newRefreshInfo), newRefreshExpire);

            Map<String, Object> result = new HashMap<>();
            result.put("accessToken", newAccessToken);
            result.put("refreshToken", newRefreshToken);
            result.put("expiresIn", ACCESS_TOKEN_EXPIRE);
            result.put("tokenType", "Bearer");

            return ActionResult.success(result);
        } catch (Exception e) {
            return ActionResult.fail("刷新 token 失败: " + e.getMessage());
        }
    }

4.验证token

bash 复制代码
/**
     * 验证访问令牌
     */
    private boolean validateAccessToken(String accessToken) {
        try {
            if (StringUtils.isBlank(accessToken)) {
                return true;
            }

            // 1. 检查 Redis 中是否存在该 token
            String tokenKey = ACCESS_TOKEN_PREFIX + accessToken;
            String tokenInfoStr = (String) redisUtil.getString(tokenKey);
            if (StringUtils.isBlank(tokenInfoStr)) {
                return true;
            }

            // 2. 验证 JWT 签名和过期时间
            String jwtSecret = getJwtSecret();
            try {
                Jwts.parser()
                        .setSigningKey(jwtSecret)
                        .parseClaimsJws(accessToken);
                return false;
            } catch (Exception e) {
                // JWT 验证失败,删除 Redis 中的记录
                redisUtil.remove(tokenKey);
                return true;
            }

        } catch (Exception e) {
            return true;
        }
    }

5.生成安全的访问令牌方法

bash 复制代码
/**
     * 生成安全的访问令牌
     */
    private String generateSecureAccessToken(StaticTokenDTO staticToken) {
        long currentTime = System.currentTimeMillis();
        long expireTime = currentTime + ACCESS_TOKEN_EXPIRE * 1000;

        // 生成随机数防止重放攻击
        String nonce = RandomUtil.randomString(16);

        // 创建 JWT payload
        Map<String, Object> claims = new HashMap<>();
        claims.put("appId", staticToken.getAppId());
        claims.put("nonce", nonce);
        claims.put("timestamp", currentTime);
        claims.put("iat", currentTime / 1000);
        claims.put("exp", expireTime / 1000);
        claims.put("code", staticToken.getCode());

        // 使用 HMAC-SHA256 签名生成 JWT
        String jwtSecret = getJwtSecret();
        String jwt = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date(currentTime))
                .setExpiration(new Date(expireTime))
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();

        return jwt;
    }

6.获取 JWT 密钥 (这个可以随便写)

bash 复制代码
/**
     * 获取 JWT 密钥
     */
    private String getJwtSecret() {

        return AuthConsts.JWT_SECRET;
    }

7.测试

bash 复制代码
@GetMapping("/getEnterpriseGradePage")
    public ActionResult<IPage<EnterpriseGradeVO>> getEnterpriseGradePage(HttpServletRequest request, Pagination page) {
        String authorization = request.getHeader("Authorization");
        if (validateAccessToken(authorization)) {
            return ActionResult.fail("无效的 accessToken");
        }

        return ActionResult.success(remoteService.getEnterpriseGradePage(page));
    }

8.主要依赖

bash 复制代码
<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
相关推荐
rabbit_pro2 分钟前
Java使用Mybatis-Plus封装动态数据源工具类
java·python·mybatis
期待のcode8 分钟前
Java虚拟机类加载机制
java·开发语言
短剑重铸之日16 分钟前
《SpringBoot4.0初识》第四篇:原生镜像
java·原生镜像·springboot4.0
程序员欣宸19 分钟前
LangChain4j实战之十二:结构化输出之三,json模式
java·人工智能·ai·json·langchain4j
咕噜企业分发小米20 分钟前
直播云服务器安全防护有哪些最新的技术趋势?
运维·服务器·安全
天若有情67336 分钟前
打破思维定式!C++参数设计新范式:让结构体替代传统参数列表
java·开发语言·c++
亲爱的非洲野猪42 分钟前
从ReentrantLock到AQS:深入解析Java并发锁的实现哲学
java·开发语言
wheelmouse778843 分钟前
如何设置VSCode打开文件Tab页签换行
java·python
yangminlei1 小时前
Spring Boot——日志介绍和配置
java·spring boot
廋到被风吹走1 小时前
【Spring】Spring Boot Starter设计:公司级监控SDK实战指南
java·spring boot·spring