最近有个小需求:实现一个IP白名单的功能;指定ip可以无需登录认证即可访问指定的接口,非指定ip需要登录认证才能访问;
安全框架用spring security+jwt,安全配置类securityConfig继承WebSecurityConfigurerAdapter中可以配置hasIpaddress白名单;
但这个只能在初始化项目时通过字符串拼接access参数,没有达到ip动态添加删除的效果;
随后在百度了N篇文章后,参考动态url权限配置,自定义了一个决策器实现了功能,代码如下:


import com.hy.common.core.redis.RedisCache;
import com.hy.openapi.apimanger.config.APIConstant;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {

    @Resource
    private RedisCache redisCache;
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        FilterInvocation fi = (FilterInvocation) o;
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        // 访问的ip
        String ipUrl = fi.getHttpRequest().getRemoteAddr();
        // 访问的接口地址
        String requestUrl = fi.getRequestUrl();
        // 获取白名单
        List<String> ipWhiteList = redisCache.getCacheList(APIConstant.IP_WHITE_LIST);
        // 判断是否是api接口
        if (antPathMatcher.match("/api/**",requestUrl)){
            // 判断是否在白名单中
            if (!ipWhiteList.contains(ipUrl)) {
                // 判断是否登录认证
                if (authentication.getPrincipal().equals("anonymousUser")){
                    throw new AccessDeniedException("认证失败");
                }
            }

        }
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

再在配置类中配置:

 @Resource
 private CustomAccessDecisionManager customAccessDecisionManager;

 ...
 httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customAccessDecisionManager);
                        return o;
                    }
                })
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/captchaImage").anonymous()

采用了redis存储、取出数据,避免重复查询数据库;
在项目初始化的时候将数据初始化进去

@Override
    public void run(ApplicationArguments args) {
        System.out.println("==========项目启动成功,开始初始化api接口、初始化IP白名单=============");
        List<String> ipWhiteList = ipWhiteListConfig.getIpWhiteList();
        // 将白名单放入redis
        redisCache.setCacheList(APIConstant.IP_WHITE_LIST,ipWhiteList);
     }

前台添加ip或删除ip的时候同时将redis里的数据更新下即可;
如果有大佬有更好的方案,还希望分享下。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐