ch12:自定义Filter实现IP限制
对于Web应用的安全来说,最常使用的方式就是Servlet Filter,Filter能够用来增加应用特定的安全层,在最终调用具体业务逻辑前,Filter可以移除潜在的恶意行为,并增强用户体验。Spring Security提供就是基于Filter提供了各项安全卡控。但是有些时候,我们还是需要开发自定义Filter以满足应用的特殊要求,例如IP限制等。
在ch04我们已经使用Spring Security基于特定的URL设定可访问的IP地址。但是这是一种静态配置,而生产过程中往往需要动态可访问IP,这时就可以实现自定义Filter,并将其注入到Spring Security Filter Chain中,作为Filter Chain的一环。
这里我们假设系统管理员限制为某些IP地址,只有同时具备管理员身份和IP地址列表时才允许使用管理员权限。
自定义Filter
任何实现了Filter接口的类都可以注入到Spring Security Filter Chain中。这里我们使用Spring Security提供的OncePerRequestFilter作为父类。该类可以保证每个请求只调用一次改Filter。子类需要实现doFilterInternal方法。
public class IpFilter extends OncePerRequestFilter {
private String targetRole;//目标角色
private List<String> authorizedIpAddresses;//IP地址列表
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && targetRole != null) {
boolean shouldCheck = false;
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority.getAuthority().equals(targetRole)) {
shouldCheck = true;
break;
}
}
if (shouldCheck && !authorizedIpAddresses.isEmpty()) {
boolean authorized = false;
for (String ipAddress : authorizedIpAddresses) {
if (request.getRemoteAddr().equals(ipAddress)) {
authorized = true;
break;
}
}
if (!authorized) {
throw new AccessDeniedException(
"Access has been denied for you IP address:" + request.getRemoteAddr());
}
}
} else {
System.out.println(
"The IPFilter should be placed after the user has been authenticated in the filter chain.");
}
filterChain.doFilter(request, response);
}
public String getTargetRole() {
return targetRole;
}
public void setTargetRole(String targetRole) {
this.targetRole = targetRole;
}
public List<String> getAuthorizedIpAddresses() {
return authorizedIpAddresses;
}
public void setAuthorizedIpAddresses(List<String> authorizedIpAddresses) {
this.authorizedIpAddresses = authorizedIpAddresses;
}
}
这里我们简单的检查了用户身份、以及访问者的IP是否为Localhost(实际生产中IP地址检查要复杂的多)。不符合要求的访问将抛出AccessDeniedException。
配置IpFilter
创建好Filter后,需要将其注入到Filter Chain中,这时就需要考虑Filter依赖关系,已决定Filter在Filter Chain中的位置。IpFilter需要获得用户认证信息,所以应该在UsernamePasswordAuthenticationFilter之后。通常自定义Filter都在Chain最后几位上,经过Spring Security层层通用过滤后,再进行个性化和具体的过滤。此外,Spring Security将FilterSecurityInterceptor作为最后一个Filter,主要是验证访问者是否有权限,包括方法级验证等。IpFilter较FilterSecurityInterceptor更具体,所以IpFilter应在后者之前。具体配置如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
......
.and().addFilterBefore(ipFilter(), FilterSecurityInterceptor.class)
......
}
private Filter ipFilter() {
List<String> ipAddresses = new ArrayList<>();
ipAddresses.add("0:0:0:0:0:0:0:1");//localhost
IpFilter ipFilter = new IpFilter();
ipFilter.setTargetRole("ROLE_ADMIN");
ipFilter.setAuthorizedIpAddresses(ipAddresses);
return ipFilter;
}
这里配置了要求具备ROLE_ADMIN的用户IP地址为Localhost。
通过本机和其他机器使用Admin用户登录,就可以测试不在IP地址列表的机器都被限制访问了。
代码示例:https://github.com/wexgundam/spring.security/tree/master/ch12