Skip to content

服务调用不鉴权注解

定义注解

java

package org.elsfs.cloud.security.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 服务调用不鉴权注解
 *
 * @author zeng
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoringAuthentication {}

定义 properties

java

package org.elsfs.cloud.security.common.properties;

import cn.hutool.core.util.ReUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.Data;
import org.elsfs.cloud.security.common.annotation.IgnoringAuthentication;
import org.elsfs.cloud.security.common.core.jwt.RsaKeyPair;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * security 参数
 *
 * @author zeng
 */
@Data
@ConfigurationProperties(prefix = "elsfs.security")
public class ElsfsSecurityProperties implements BeanFactoryAware {
  private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
  private BeanFactory beanFactory;
  private final List<RequestMatcher> requestMatchers = new ArrayList<>();
  private static final String[] DEFAULT_IGNORE_URLS =
      new String[] {
        "/**.html",
        "/**.css",
        "/css/**.css",
        "/js/**.js",
        "/templates/css/**",
        "/webjars/**",
        "/actuator/**",
        "/error",
        "/token/**",
        "/favicon.ico",
        "/img/**",
        "/login",
        "/error",
        //      PhonePasswordAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI
      };
  private String issuer;
  private String consentPage = "/oauth2/consent";
  private Set<RsaKeyPair> keyPairs = new HashSet<>();
  private Cors cors = new Cors();
  private Map<RequestMethod, String[]> ignoreHttpMethod =
      Map.of(
          RequestMethod.GET,
          new String[] {},
          RequestMethod.POST,
          new String[] {},
          RequestMethod.DELETE,
          new String[] {},
          RequestMethod.PUT,
          new String[] {});

  /**
   * 获取所有忽略认证的RequestMatcher
   *
   * @return RequestMatcher
   */
  public RequestMatcher getRequestMatcher() {
    List<RequestMatcher> matcherList = new ArrayList<>();
    for (String ignoreUrl : DEFAULT_IGNORE_URLS) {
      matcherList.add(AntPathRequestMatcher.antMatcher(HttpMethod.GET, ignoreUrl));
    }
    Map<RequestMethod, String[]> method = getIgnoreHttpMethod();
    for (RequestMethod httpMethod : method.keySet()) {
      for (String path : method.get(httpMethod)) {
        matcherList.add(new AntPathRequestMatcher(path, httpMethod.name()));
      }
    }
    afterPropertiesSet();
    matcherList.addAll(requestMatchers);
    return new OrRequestMatcher(matcherList);
  }

  protected void afterPropertiesSet() {
    RequestMappingHandlerMapping mapping =
        beanFactory.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
    Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
    for (RequestMappingInfo info : map.keySet()) {
      // 获取方法
      HandlerMethod handlerMethod = map.get(info);
      IgnoringAuthentication method =
          AnnotationUtils.findAnnotation(handlerMethod.getMethod(), IgnoringAuthentication.class);
      // 获取请求方式
      Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
      String methodStr = methods.isEmpty() ? null : methods.iterator().next().name();
      Optional.ofNullable(method)
          .ifPresent(
              ignoringAuthentication ->
                  Objects.requireNonNull(info.getPathPatternsCondition())
                      .getPatternValues()
                      .forEach(
                          patternValue ->
                              requestMatchers.add(
                                  new AntPathRequestMatcher(patternValue, methodStr))));

      // 获取类上边的注解, 替代path variable 为 *
      IgnoringAuthentication controller =
          AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), IgnoringAuthentication.class);
      Optional.ofNullable(controller)
          .ifPresent(
              inner ->
                  Objects.requireNonNull(info.getPathPatternsCondition())
                      .getPatternValues()
                      .forEach(
                          uri ->
                              requestMatchers.add(
                                  new AntPathRequestMatcher(
                                      ReUtil.replaceAll(uri, PATTERN, "*")))));
    }
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
  }

  /** Cors */
  @Data
  public static class Cors {
    private boolean enabled = false;

    /**
     * 跨域请求允许的源 例如:
     *
     * <ul>
     *   <li>特定域,例如 {@code https://domain1.com}
     *   <li>以逗号分隔的特定域列表:{@code https://a1.com,https://a2.com}
     *   <li>CORS为所有原点定义了特殊值{@code *}
     * </ul>
     */
    private List<String> origins = List.of(CorsConfiguration.ALL);

    /**
     * 替代 origins,它支持更灵活的起源模式,除了端口列表之外,主机名中的任何位置都有“*”。示例:
     *
     * <ul>
     *   <li>https://*.domain1.com--以domain1.com结尾的域
     *   <li>https://*.domain1.com:[8080081]--端口8080或端口8081上以domain1.com结尾的域
     *   <li>https://*.domain1.com:[*]--任何端口上以domain1.com结尾的域,包括默认端口
     *   <li>*
     * </ul>
     */
    private List<String> allowedOriginPatterns = List.of(CorsConfiguration.ALL);

    /** 允许跨域的请求头 */
    private List<String> allowedHeaders = List.of(CorsConfiguration.ALL);

    /**
     * 允许跨域的请求方式
     *
     * @see org.springframework.http.HttpMethod
     */
    private List<String> allowedMethods = List.of(CorsConfiguration.ALL);
  }
}

实现

java
@Configuration
@EnableConfigurationProperties(ElsfsSecurityProperties.class)
class config{
  private ElsfsSecurityProperties getElsfsSecurityProperties(HttpSecurity httpSecurity) {
    return getApplicationContext(httpSecurity).getBean(ElsfsSecurityProperties.class);
  }
  
  /**
   * 添加认证的请求
   *
   * @param http http
   * @throws Exception 配置异常
   */
     @Bean
  @Order(2)
   SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
      ElsfsSecurityProperties properties = getElsfsSecurityProperties(http);
      http.authorizeHttpRequests(
          (authorize) -> {
            authorize.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
            authorize.requestMatchers(properties.getRequestMatcher()).permitAll();
  
            authorize.anyRequest().authenticated();
          });
     return http.build();
  }
 }

使用

java
/**
 * text
 *
 * @author zeng
 */
@RestController
public class TestController {
  /**
   * test
   *
   * @return r
   */
  @GetMapping("/test")
  @IgnoringAuthentication
  public R<?> test() {
    return R.success();
  }
}

版权声明