カスタムアノテーションを活用して、SpringSecurityプロジェクトのインターフェイスを並べます

スプリングセキュリティをセキュリティフレームワークとして使用する実際のプロジェクトでは、匿名でアクセスできるようにいくつかのインターフェイスを解放する必要があるビジネス要件に遭遇します。しかし、リリースする必要があるときはいつでも、セキュリティ構成クラスでそれを変更する必要があります。これは非常にエレガントではないと感じます。\

例:これ:

写真

写真

そこで、注釈をカスタマイズして、インターフェイスに匿名でアクセスできるようにします。要件を実装する前に、まずセキュリティについての2つの考え方を理解しましょう。

1つ目は 、次configure(WebSecurity web)のようにメソッドでリリースを構成することです。

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

2番目の方法は 、次configure(HttpSecurity http)の方法で構成することです。

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

2つの方法の最大の違いは、最初の方法はSpring Securityフィルターチェーンに従わないのに対し、2番目の方法はSpring Securityフィルターチェーンに続くことであり、フィルターチェーンでは要求が解放されます。Spring Bootを学習している場合は、長年にわたってシリアル化され、継続的に更新されている無料のチュートリアルをお勧めします:blog.didispace.com/spring-boot…

Spring Securityを使用すると、検証なしで最初の方法を使用して一部のリソースを追加で解放できます。たとえば、フロントエンドページの静的リソースは、最初の方法に従って構成および解放できます。

一部のリソースを解放するには、ログインインターフェイスなどの2番目の方法を使用する必要があります。ご存知のとおり、ログインインターフェイスも公開する必要があり、ログインせずにアクセスできますが、最初の方法でログインインターフェイスを公開することはできません。ログイン要求はSpringSecurityフィルターチェーンを通過する必要があります。特定のログインプロセスを知りたい場合は、自分でBaiduを使用できます。

セキュリティの2つのリリース戦略を理解した後、実装を開始します

最初にカスタムアノテーションを作成します

@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わかりやすくするために、ワークフローについて簡単に説明します。ソースコードを見ることによって

写真

写真

継承関係は上の図に示されています。

AbstractHandlerMethodMappingInitializingBean インターフェイスを実装します

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()方法は、すべてのHandlerMappingを取得することです。

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