Используйте пользовательские аннотации для оформления интерфейса проекта Spring Security.

В реальном проекте, использующем springsecurity в качестве основы безопасности, мы столкнемся с бизнес-требованиями, которые должны выпустить некоторые интерфейсы, чтобы к ним можно было получить анонимный доступ. Но всякий раз, когда вам нужно выпустить, вам нужно изменить его в классе конфигурации безопасности, что кажется очень неэлегантным. \

Например это:

картина

картина

Поэтому я хочу сделать анонимный доступ к интерфейсу, настроив аннотацию. Прежде чем внедрять требования, давайте сначала разберемся с двумя способами мышления о безопасности.

Первый —  configure(WebSecurity web)настроить релиз в методе, вот так:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
}
复制代码

Второй способ —  configure(HttpSecurity http)настроить его в методе:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
 httpSecurity
    .authorizeRequests()
          .antMatchers("/hello").permitAll()
          .anyRequest().authenticated()
}
复制代码

Самая большая разница между этими двумя методами заключается в том, что первый метод не следует цепочке фильтров Spring Security, а второй метод следует цепочке фильтров Spring Security, и в цепочке фильтров запрос освобождается. Если вы изучаете Spring Boot, я рекомендую бесплатный учебник, который выпускается уже много лет и продолжает обновляться: blog.didispace.com/spring-boot…

Когда мы используем Spring Security, некоторые ресурсы могут быть освобождены дополнительно по первому способу без проверки, например, статические ресурсы страницы фронтенда могут быть настроены и освобождены по первому способу.

Для освобождения некоторых ресурсов необходимо использовать второй метод, например интерфейс входа в систему. Как мы все знаем, интерфейс входа в систему также должен быть открыт, и к нему можно получить доступ без входа в систему, но мы не можем открыть интерфейс входа первым способом.Запрос на вход должен пройти через цепочку фильтров Spring Security, потому что в этом процесс, есть и другие вещи, которые нужно сделать.Если вы хотите узнать конкретный процесс входа в систему, вы можете самостоятельно использовать Baidu.

Поняв две стратегии выпуска безопасности, мы начинаем реализовывать

Сначала создайте пользовательскую аннотацию

@Target({ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface IgnoreAuth {
}
复制代码

Вот описание @Target({ElementType.METHOD})моей реализации, аннотации можно ставить только на @RequestMappingметоды с аннотациями. В частности, почему следующая реализация поймет после ее прочтения.

Далее создайте класс конфигурации безопасности SecurityConfig и наследуйтеWebSecurityConfigurerAdapter

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * @ description: 使用这种方式放行的接口,不走 Spring Security 过滤器链,
     *                无法通过 SecurityContextHolder 获取到登录用户信息的,
     *                因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。
     * @ dateTime: 2021/7/19 10:22
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        WebSecurity and = web.ignoring().and();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        handlerMethods.forEach((info, method) -> {
            // 带IgnoreAuth注解的方法直接放行
            if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) {
                // 根据请求类型做不同的处理
                info.getMethodsCondition().getMethods().forEach(requestMethod -> {
                    switch (requestMethod) {
                        case GET:
                            // getPatternsCondition得到请求url数组,遍历处理
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                // 放行
                                and.ignoring().antMatchers(HttpMethod.GET, pattern);
                            });
                            break;
                        case POST:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.POST, pattern);
                            });
                            break;
                        case DELETE:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.DELETE, pattern);
                            });
                            break;
                        case PUT:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.PUT, pattern);
                            });
                            break;
                        default:
                            break;
                    }
                });
            }
        });
    }
}
复制代码

Используя классы, предоставляемые Spring здесь RequestMappingHandlerMapping, мы можем requestMappingHandlerMapping.getHandlerMethods();получить всю RequestMappingInfoинформацию.

Ниже приведена часть исходного кода, но не читайте ее, вы можете углубить свое понимание после прочтения.

Вот краткое описание RequestMappingHandlerMappingрабочего процесса для простоты понимания. Глядя на исходный код

картина

картина

Отношения наследования показаны на рисунке выше.

AbstractHandlerMethodMappingреализует InitializingBean интерфейс

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}
复制代码

AbstractHandlerMethodMappingКласс инициализируется afterPropertiesSetвызовом методаinitHandlerMethods

 public void afterPropertiesSet() {
        this.initHandlerMethods();
    }

    protected void initHandlerMethods() {
        String[] var1 = this.getCandidateBeanNames();
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String beanName = var1[var3];
            if (!beanName.startsWith("scopedTarget.")) {
                this.processCandidateBean(beanName);
            }
        }

        this.handlerMethodsInitialized(this.getHandlerMethods());
    }
复制代码

Вызовите processCandidateBeanметод еще раз:

 protected void processCandidateBean(String beanName) {
        Class beanType = null;

        try {
            beanType = this.obtainApplicationContext().getType(beanName);
        } catch (Throwable var4) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
            }
        }

        if (beanType != null && this.isHandler(beanType)) {
            this.detectHandlerMethods(beanName);
        }

    }
复制代码

Вызов метода isHandler в методе не является requestHandlerметодом, вы можете видеть, что исходный код передается RequestMapping, а для суждения используется аннотация Controller.

protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
    }
复制代码

После принятия решения вызывающий detectHandlerMethods метод регистрирует обработчик в кэше HandlerMethod. Если вы изучаете Spring Boot, я рекомендую бесплатный учебник, который выпускается уже много лет и продолжает обновляться: blog.didispace.com/spring-boot…

protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
                try {
                    return this.getMappingForMethod(method, userType);
                } catch (Throwable var4) {
                    throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
                }
            });
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(this.formatMappings(userType, methods));
            }

            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                this.registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }

    }
复制代码

registerHandlerMethodОбработчик помещается в private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap();карту методом .

Метод requestMappingHandlerMapping.getHandlerMethods()заключается в том, чтобы получить все HandlerMappings.

public Map<T, HandlerMethod> getHandlerMethods() {
    this.mappingRegistry.acquireReadLock();

    Map var1;
    try {
        var1 = Collections.unmodifiableMap(this.mappingRegistry.getMappings());
    } finally {
        this.mappingRegistry.releaseReadLock();
    }

    return var1;
}
复制代码

Последний шаг — просмотреть карту, определить, IgnoreAuth.classаннотирована она или нет, а затем освободить ее для различных методов запроса.

handlerMethods.forEach((info, method) -> {
            // 带IgnoreAuth注解的方法直接放行
            if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) {
                // 根据请求类型做不同的处理
                info.getMethodsCondition().getMethods().forEach(requestMethod -> {
                    switch (requestMethod) {
                        case GET:
                            // getPatternsCondition得到请求url数组,遍历处理
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                // 放行
                                and.ignoring().antMatchers(HttpMethod.GET, pattern);
                            });
                            break;
                        case POST:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.POST, pattern);
                            });
                            break;
                        case DELETE:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.DELETE, pattern);
                            });
                            break;
                        case PUT:
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                and.ignoring().antMatchers(HttpMethod.PUT, pattern);
                            });
                            break;
                        default:
                            break;
                    }
                });
            }
        });
复制代码

Увидев это, вы можете понять мой первоначальный акцент на маркировке @RequestMappingметодов аннотациями. Здесь я использую configure(WebSecurity web)метод освобождения. Он не следует цепочке фильтров безопасности и не может  SecurityContextHolder получить информацию о пользователе для входа.На эту проблему следует обратить внимание.

рекомендация

отjuejin.im/post/7086632592715284487