zoukankan      html  css  js  c++  java
  • spring boot:接口站增加api版本号后的安全增强(spring boot 2.3.3)

    一,接口站增加api版本号后需要做安全保障?

    1,如果有接口需要登录后才能访问的,

      需要用spring security增加授权

    2,接口站需要增加api版本号的检验,必须是系统中定义的版本号才能访问,

       避免乱填值刷接口的情况

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,演示项目的相关信息

    1,项目地址:

    https://github.com/liuhongdi/apiversionsecurity

    2,功能说明:

              演示了接口站增加api版本号后的安全增强

    3,项目结构:如图:

    三,配置文件说明

    1,pom.xml

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

    2,application.properties

    #error
    server.error.include-stacktrace=always
    #errorlog
    logging.level.org.springframework.web=trace

    四,java代码说明

    1,SecurityConfig.java

    @Configuration
    @EnableWebSecurity
     public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
         @Override
         protected void configure(HttpSecurity http) throws Exception {
                      //login和logout
                      http.formLogin()
                            .defaultSuccessUrl("/v2/home/home")
                            .failureUrl("/login-error.html")
                            .permitAll()
                            .and()
                            .logout();
    
                      //匹配的页面,符合限制才可访问
                      http.authorizeRequests()
                     .antMatchers("/v*/home/**").hasAnyRole("ADMIN","DEV")
                     .antMatchers("/v*/goods/**").hasAnyRole("ADMIN","USER");
                      //剩下的页面,允许访问
                     http.authorizeRequests().anyRequest().permitAll();
         }
    
         @Autowired
         public  void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
             //添加两个账号用来做测试
             auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                     .withUser("lhdadmin")
                     .password(new BCryptPasswordEncoder().encode("123456"))
                     .roles("ADMIN","USER")
                     .and()
                     .withUser("lhduser")
                     .password(new BCryptPasswordEncoder().encode("123456"))
                     .roles("USER");
         }
     }

    2,Constants.java

    public class Constants {
        //api version
        public final static List API_VERSION_LIST = Arrays.asList("1","1.0","1.5","1.8","2","2.0");
    }

    定义了api版本号常量

    3,ApiVersionCondition.java

    //实现RequestCondition
    public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
        //api版本号
        private String apiVersion;
        //版本号的格式,如: /v[1-n]/api/test or /v1.5/home/api
        private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("/v((\d+\.\d+)|(\d+))/");
        public ApiVersionCondition(String apiVersion) {
            this.apiVersion = apiVersion;
        }
    
        //将不同的筛选条件进行合并
        @Override
        public ApiVersionCondition combine(ApiVersionCondition other) {
            // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
            return new ApiVersionCondition(other.getApiVersion());
        }
    
        //版本比对,用于排序
        @Override
        public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
            //优先匹配最新版本号
            return compareTo(other.getApiVersion(),this.apiVersion)?1:-1;
        }
    
        //获得符合匹配条件的ApiVersionCondition
        @Override
        public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
            Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
            if (m.find()) {
                String version = m.group(1);
                //如果版本号不是list中则返回
                if (!Constants.API_VERSION_LIST.contains(version)) {
                    return null;
                }
                if (compareTo(version,this.apiVersion)){
                    return this;
                }
            }
            return null;
        }
        //compare version
        private boolean compareTo(String version1,String version2){
            if (!version1.contains(".")) {
                version1 += ".0";
            }
            if (!version2.contains(".")) {
                version2 += ".0";
            }
            String[] split1 = version1.split("\.");
            String[] split2 = version2.split("\.");
            for (int i = 0; i < split1.length; i++) {
                if (Integer.parseInt(split1[i])<Integer.parseInt(split2[i])){
                    return false;
                }
            }
            return true;
        }
    
        public String getApiVersion() {
            return apiVersion;
        }
    }

    对版本号的解析和处理

    4,ApiVersionRequestMappingHandlerMapping.java

    //扩展RequestMappingHandlerMapping
    public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    
        //类上有 @ApiVersion注解时生效
        @Override
        protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
            ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
            return createRequestCondition(apiVersion);
        }
    
        //方法上有 @ApiVersion注解时生效
        @Override
        protected RequestCondition<?> getCustomMethodCondition(Method method) {
            ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
            return createRequestCondition(apiVersion);
        }
    
        //返回ApiVersionCondition
        private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) {
            return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
        }
    }

    定义注解的生效条件

    5,WebMvcConfig.java

    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        //在获取RequestMappingHandlerMapping时
        //返回我们自定义的ApiVersionRequestMappingHandlerMapping
        @Override
        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
            return new ApiVersionRequestMappingHandlerMapping();
        }
    }

    使自定义的版本号解析生效

    6,ApiVersion.java

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ApiVersion {
        //版本号的值,从1开始
        String value() default "1";
    }

    自定义版本号的注解

    7,HomeController.java

    @RestController
    @RequestMapping("/{version}/home")
    public class HomeController {
    
        //匹配版本v1的访问
        @ApiVersion("1")
        @GetMapping
        @RequestMapping("/home")
        public String home01(@PathVariable String version) {
            return "home v1 : version:" + version;
        }
    
        //匹配版本v2的访问
        @ApiVersion("2.0")
        @GetMapping
        @RequestMapping("/home")
        public String home02(@PathVariable String version) {
            String username = SessionUtil.getCurrentUserName();
            String url = ServletUtil.getRequest().getRequestURL().toString();
            return "home v2 version: " + version+":username:"+username+";url:"+url;
        }
    
        //匹配版本v1.5-2.0的访问
        @ApiVersion("1.5")
        @GetMapping
        @RequestMapping("/home")
        public String home15(@PathVariable String version) {
            return "home v1.5 version: " + version;
        }
    
    }

    7,其他非关键代码请访问github

    五,测试效果

    1,有权限访问的演示:

    访问:

    http://127.0.0.1:8080/v2/home/home

    会跳转到登录页面:

     我们用lhdadmin这个账号登录:

    可以正常访问

    用未定义的版本号访问时会报错,如图:

     如果一个版本号在方法没有定义,则会访问到相应的下一个版本:

    如图:

     没有方法标注1.8,但有方法上标注了1.5,所以访问到了注解版本号1.5的这个方法

    2,演示无权限的访问:

    http://127.0.0.1:8080/v1.8/goods/goodsone

    会跳转到登录页面

     用lhduser这个账号登录

     可以访问goods接口

    因为没有访问home接口的授权,所以访问时会报错,如图:

    六,查看spring boot版本

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.3.3.RELEASE)
  • 相关阅读:
    0808 HTML 基础
    2016.8.3 C#基础 结构体,枚举类型
    2016.8.1 C#基础 传值
    2016.7.22
    2016.7.20
    2016.7.31C#基础 函数
    2016.07.30C#基础 特殊集合
    2016.7.28C#基础 集合
    个人项目网页3
    个人项目网页2
  • 原文地址:https://www.cnblogs.com/architectforest/p/13647855.html
Copyright © 2011-2022 走看看