前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何利用自定义注解放行 Spring Security 项目的接口

如何利用自定义注解放行 Spring Security 项目的接口

作者头像
程序猿DD
发布2022-03-16 11:10:19
6150
发布2022-03-16 11:10:19
举报
文章被收录于专栏:程序猿DD

在实际项目中使用到了springsecurity作为安全框架,我们会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,感觉非常的不优雅。

例如这样:

图片

所以想通过自定义一个注解,来进行接口匿名访问。在实现需求前,我们先了解一下security的两种方行思路。

第一种就是在 configure(WebSecurity web)方法中配置放行,像下面这样:

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

第二种方式是在 configure(HttpSecurity http)方法中进行配置:

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

两种方式最大的区别在于,第一种方式是不走 Spring Security 过滤器链,而第二种方式走 Spring Security 过滤器链,在过滤器链中,给请求放行。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

在我们使用 Spring Security 的时候,有的资源可以使用第一种方式额外放行,不需要验证,例如前端页面的静态资源,就可以按照第一种方式配置放行。

有的资源放行,则必须使用第二种方式,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需要登录就能访问到的,但是我们却不能将登录接口用第一种方式暴露出来,登录请求必须要走 Spring Security 过滤器链,因为在这个过程中,还有其他事情要做,具体的登录流程想了解的可以自行百度。

了解完了security的两种放行策略后,我们开始实现

首先创建一个自定义注解

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

这里说明一下,@Target({ElementType.METHOD})我的实现方式,注解只能标记在带有@RequestMapping注解的方法上。具体为什么下面的实现方式看完就懂了。

接下来创建一个security的配置类SecurityConfig并继承WebSecurityConfigurerAdapter

代码语言:javascript
复制
@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信息。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

以下是源码部分,可不看,看了可以加深理解

这里简单说一下RequestMappingHandlerMapping的工作流程,便于理解。我们通过翻看源码

图片

继承关系如上图所示。

AbstractHandlerMethodMapping实现了InitializingBean 接口

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

AbstractHandlerMethodMapping类中通过afterPropertiesSet方法调用initHandlerMethods进行初始化

代码语言:javascript
复制
 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方法:

代码语言:javascript
复制
 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 注解进行判断的。

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

判断通过后,调用detectHandlerMethods 方法将handler注册到HandlerMethod的缓存中。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

代码语言:javascript
复制
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方法将handler放到private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap();map中。

requestMappingHandlerMapping.getHandlerMethods()方法就是获取所有的HandlerMapping。

代码语言:javascript
复制
public Map<T, HandlerMethod> getHandlerMethods() {
    this.mappingRegistry.acquireReadLock();

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

    return var1;
}

最后就是对map进行遍历,判断是否带有IgnoreAuth.class注解,然后针对不同的请求方式进行放行。

代码语言:javascript
复制
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)的放行方式。它是不走security的过滤链,是无法通过 SecurityContextHolder 获取到登录用户信息的,这点问题是需要注意的。

来源:https://blog.csdn.net/weixin_45089791/article/details/118890274

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取

推荐阅读

··································

你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书、创过业、国企4年互联网6年。10年前毕业加入宇宙行,工资不高、也不算太忙,业余坚持研究技术和做自己想做的东西。4年后离开国企,加入永辉互联网板块的创业团队,从开发、到架构、到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。

点击阅读原文,送你免费Spring Boot教程

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿DD 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 了解完了security的两种放行策略后,我们开始实现
  • 推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档