在web项目交付阶段,很多交付的小伙伴们会面临的一个问题就是安全漏洞扫描,关于安全漏洞问题,可以分为很多种,但从扫描者的角度,一旦他们认为你的后端服务接口暴露的太多,或过于明显,或没有前置的安全措施(通过登录的方式)就能拿到后台的数据,这样显然是不合理的,一旦扫出这样的接口,那么面临的一个问题就是,解决接口暴露的问题
关于这个问题,其实说起来,还是跟系统在一开始的规划设计阶段的安全考虑不够完备,比如你的后台项目是否需要做必要的鉴权,是否集成了第三方安全框架等,如果在项目框架搭建之初,就能考虑到这些问题,相信在后续的交付阶段,就不必担心接口的安全漏洞问题
但不管如何,一旦出现了,既然不能推倒重来,我们还必须要想办法快速补救,下面就本人在项目交付阶段的一点零碎的经验,针对这个问题,做一点技术实现方案的总结
这种方式最简单,对大多数程序员来说,也最容易实现,只需要在一个自定义过滤器中,针对所有请求后端服务的url做拦截,对后端所有的URL进行分类,不同的url必须要经过什么样的校验才能通过,这种方式比较简单,就不再做代码层面的展示了
先贴上代码
package com.congge.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
public static Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("执行 preHandle:{}",request.getRequestURI());
//TODO 这里可以是token,权限等前置业务的处理......
response.getWriter().write("you have no access");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("执行 preHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("执行 afterCompletion");
}
}
package com.congge.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class MyWebmvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/user/**", "/admin/**", "/account/**") //哪些路径要拦截
.excludePathPatterns("/open/api/**"); //哪些逻辑不需要做拦截
}
/**
* 静态资源的访问
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/templates/**")
.addResourceLocations("classpath:/templates/");
super.addResourceHandlers(registry);
}
}
定义一个拦截器,实现HandlerInterceptor接口,在重新的方法中,编写自身校验权限,token等逻辑,如果返回false,请求直接结束,否则可以进入后续的接口资源访问,以文中的配置为例,我们分别访问一下2个接口
上面这种方式的改造成本比较小,实现难度也很小,可以作为第一选择
相信很多小伙伴都用过shiro,shiro是一个小巧适用的安全框架,但实际上来说,在和后端项目进行集成时,很多前置的配置工作要做,而且和数据库的权限认证那一套的表结合的很紧密,如果不是在设计之初就集成的话,这个改造的难度和复杂度还是比较大的,慎用
一听到spring-security,很多同学开始谈虎色变了,spring-security在后端的框架中算是比较复杂,也比较重型的框架了,学习成本是比较高的,但经过本人的研究发现,如果并不打算完全将spring-security的安全体系替换现有的代码逻辑时,可以借助其部分针对接口资源的权限控制功能,完全可以达到本篇问题的目的,下面通过2种方式来快速说明下
导入security依赖
org.springframework.boot
spring-boot-starter-security
方式1:直接引用上面的依赖之后,在配置文件中做简单的配置即可
#手动设置登录用户名和密码
spring.security.user.name=admin
spring.security.user.password=admin
引入依赖之后,项目启动之后,访问后端接口时,所有的请求都要经过spring-security的默认登录页面,因此上述配置文件中配置的用户名和密码即为登录的用户名和密码
当我们再次访问上面的接口时,就会弹出下面的登录页面
输入:root/root,即可正常访问接口了
但这种方式的使用场景比较有限,使用的时候请慎重考虑
方式2:使用spring-security的安全配置策略
spring-security集成了很多功能,比如像权限,角色控制等,一般来说,任何系统的登录功能是必不可少的,因此可以借助系统现有的登录登录,再融入spring-security的登录认证即可满足
实现起来,主要包括如下几点,
1)自定义UserDetailsService,即实现UserDetailsService接口
@Configuration("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
MyUser myUser = userMapper.selectOne(queryWrapper);
if(myUser==null){
throw new RuntimeException("该用户不存在");
}
List roles = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(username,new BCryptPasswordEncoder().encode(myUser.getPassword()),roles);
}
}
2)自定义配置类,继承WebSecurityConfigurerAdapter,根据需要重写里面的相关方法即可
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService userDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义的登录界面
.loginProcessingUrl("/user/login") //登录提交的接口
.defaultSuccessUrl("/zcy/index").permitAll() //登录成功之后默认的跳转路径
.and().authorizeRequests() //自定义受保护的URL资源
.antMatchers("/","/user/login","/open/api/**")
.permitAll() //设置那些URL路径可以直接访问,不需要认证
.anyRequest().authenticated() //除了上面其他的URL资源都受保护
.and()
.csrf().disable(); //关闭CSRF防护
}
}
关于上面的这段配置,相信的使用说明,有兴趣的同学可以参考我的专栏文章:spring-security
总体的逻辑就是,在自定义的MyUserDetailService 中,完成登录的业务,只有查到访问资源的请求用户存在才会进入到下一步,当然在这个类中,可以结合现有系统的角色,权限体系做进一步的整合,而MySecurityConfig这个类,控制对访问的URL做保护,规定了哪些URL需要走登录认证,哪些可以不用认证等配置信息
启动项目之后,让我们来做一下测试吧,分别访问一下下面的接口
http://localhost:8081/open/api/user-info
http://localhost:8081/account/get/list
访问接口2的时候,弹出了如下的自定义登录页面
在数据库中有这样一个用户,我们输入用户名和密码,就可以访问到该接口了
本篇总结了几种常用的限制访问后端URL的方式,希望对看到的小伙伴们有用,本篇到此结束,最后感谢观看!