對于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