SpringSecurity认证与授权 || 实现自定义注解放行可匿名访问的接口

2,600 阅读3分钟

点关注不迷路,欢迎关注点赞评论!

最近在做一个关于认证与授权的功能模块,需要使用自定义注解完成匿名访问的接口放行功能。想必各位大佬都知道或者了解,springsecurity中三个configure方法

image.png

  1. configure(AuthenticationManagerBuilder auth)
  2. configure(HttpSecurity http)
  3. configure(WebSecurity web) 使用web.ignoring().antMatchers()方法;,可以实现不走 Spring Security 过滤器链
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login");
    }
}

一、首先根据官网搭一个基础的环境,加入spring-boot-starter-security和spring-boot-starter-web依赖

修改pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

二、配置基础的SecurityConfig和WebMvcConfig以及登录页

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                .authorizeRequests()
                .anyRequest()// 所有请求全部需要鉴权认证
                .authenticated();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("user")
                        .password("password")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }

}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }

}

在templates下添加login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

三、新建controller测试一把

@RestController
@RequestMapping
public class TestController {

    @GetMapping("test")
    public String test() {
        return "直接过滤";
    }

    @GetMapping("test1")
    public String test1() {
        return "需要登录";
    }
}

四、访问一下localhost:8080/test和test1接口,可以看到会跳转到登录页,说明我们的接口都是被拦截了的

屏幕截图 2021-07-04 140120.png

五、现在我们通过 web.ignoring().antMatchers来把test放行,放行配置如下

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 此方法可以实现不走 Spring Security 过滤器链
        web.ignoring().antMatchers(HttpMethod.GET, "/test");
    }

六、再试下test接口访问,直接访问成功

屏幕截图 2021-07-04 141118.png

而test1接口这个时候访问还是需要登录,说明我们的放行配置起到了作用

image.png

这个时候我们又要思考了,当项目越来越大,接口越来越多的时候怎么办呢?难道一直改代码?这样的方式恐怕不妥啊!问题不大,我们现在试一试用注解的方式来放行接口!

思路:把所有带这个注解的方法放行了不就行了吗

开始我们的操作

新建一个IgnoreAuth的注解

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

现在有了注解,怎么获取到所有带这个注解的方法呢?

spring提供了RequestMappingHandlerMapping类,可以帮助我们,来获得所有的URL 通过继承(这里就不给大家看源码了,感兴趣的可以自己去看),可以调用getHandlerMethods()来获取
先debug看下获取到的数据是什么样的

@Override
    public void configure(WebSecurity web) throws Exception {
        WebSecurity and = web.ignoring().and();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = 
                                                            handlerMapping.getHandlerMethods();
        }

启动项目,看到handlerMethods中包含了key(请求的类型,请求url)value(具体的方法)

屏幕截图 2021-07-04 144729.png

那现在有了所有的url方法,我们只需要将带有IgnoreAuth注解方法筛选出来,并且直接放行就行了

handlerMethods.forEach((info, method) -> {
            // 带IgnoreAuth注解的方法直接放行
            if (!Objects.isNull(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;
                    }
                    ....
                });
            }
        });
}

在test方法上打上我们的自定义注解IgnoreAuth

    @IgnoreAuth
    @GetMapping("test")
    public String test() {
        return "直接过滤";
    }

写完代码,重启项目,我们再测试下,搞定! 屏幕截图 2021-07-04 141118.png

当然,我这里只是其中一种实现方式,有哪儿不足的地方希望大家能在评论中指出来,谢谢大家! image.png

写文真不容易,裂开!