Point attention is not lost, welcome attention praise comments!

Recently, I am working on a functional module about authentication and authorization, which needs to use custom annotations to complete the interface release function of anonymous access. There are three configure methods in springsecurity that you all know about

  1. configure(AuthenticationManagerBuilder auth)
  2. configure(HttpSecurity http)
  3. configure(WebSecurity web)

Ignoring ().antmatchers (); To avoid the Spring Security filter chain

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login"); }}Copy the code

First, build a basic environment according to the official website, add spring-boot-starter-security and spring-boot-starter-web dependency

Modified 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 - the 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>
Copy the code

Configure the basic SecurityConfig and WebMvcConfig and the login page

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

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

        httpSecurity
                .authorizeRequests()
                .anyRequest()// All requests require authentication
                .authenticated();
    }

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

        return newInMemoryUserDetailsManager(user); }}Copy the code
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

Add login. HTML under templates

<! DOCTYPEhtml>
<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>
Copy the code

3. Create a new Controller to test

@RestController
@RequestMapping
public class TestController {

    @GetMapping("test")
    public String test(a) {
        return "Direct filtration";
    }

    @GetMapping("test1")
    public String test1(a) {
        return "Login required"; }}Copy the code

Localhost :8080/test and test1 are blocked by the localhost:8080/test interface

Ignoring ().antmatchers (

    @Override
    public void configure(WebSecurity web) throws Exception {
        // This method can be implemented without the Spring Security filter chain
        web.ignoring().antMatchers(HttpMethod.GET, "/test");
    }
Copy the code

6. Try the test interface access again. The direct access succeeds

However, access to interface test1 still requires login at this time, indicating that our release configuration plays a role

At this point, we have to think about, what happens when the project gets bigger and bigger and there are more and more interfaces? Do you keep changing the code? I’m afraid this way is not right! The problem is ok, we now try to annotate the way to release interface!

Just pass all methods with this annotation

Let’s start our operation

Create a new IgnoreAuth annotation

@Target(ElementType.METHOD) METHOD is annotable at the METHOD level
@Retention(RetentionPolicy.RUNTIME) // At which stage annotations are executed
@Documented // Generate a document
public @interface IgnoreAuth {
}
Copy the code

Now that I have an annotation, how do I get all the methods with that annotation?

Spring provides RequestMappingHandlerMapping class, can help us to get all the URL through inheritance (here is not for you to look at the source code, interested can go to see). You can call getHandlerMethods() to get debug

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

Start the project and see that handlerMethods includes key (request type, request URL) value(specific method)

So now that we have all the URL methods, we just need to filter out the IgnoreAuth annotation methods and pass them

handlerMethods.forEach((info, method) -> {
            // Methods annotated with IgnoreAuth are allowed directly
            if(! Objects.isNull(method.getMethodAnnotation(IgnoreAuth.class))) {// Do different processing according to the request type
                info.getMethodsCondition().getMethods().forEach(requestMethod -> {
                    switch (requestMethod) {
                        case GET:
                            // getPatternsCondition gets the array of request urls to iterate over
                            info.getPatternsCondition().getPatterns().forEach(pattern -> {
                                / / release
                                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; }... }); }}); }Copy the code

Add our custom IgnoreAuth annotation to the test method

    @IgnoreAuth
    @GetMapping("test")
    public String test(a) {
        return "Direct filtration";
    }
Copy the code

Write the code, restart the project, and we’ll test it again. Done!

Of course, here is only one of the implementation methods, I hope you can point out the shortcomings in the comments, thank you!

It’s not easy to write. Crack!