清单 6. 对方法调用进行保护
<bean id="userSalarySecurity"
class="org.springframework.security.access.intercept.aspectj.
AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="securityMetadataSource">
<value>
mycompany.service.UserService.raiseSalary=ROLE_MANAGER
</value>
</property>
</bean>
如 代码清单 6所示,通过 mycompany.service.UserService.raiseSalary=ROLE_MANAGER声明了 mycompany.service.UserService类的 raiseSalary方法只有具有角色 ROLE_MANAGER的用户才能执行。这就使得只具有角色 ROLE_USER的用户无法调用此方法。
不过仅对方法名称进行权限控制并不能解决另外的一些问题。比如在第一个示例应用中的增加工资的实现是通过发送 HTTP POST 请求到 salary.do这个 URL 来完成的。salary.do对应的控制器 mycompany.controller.SalaryController会调用 mycompany.service.UserService类的 raiseSalary方法来完成增加工资的操作。存在的一种安全漏洞是具有 ROLE_MANAGER角色的用户可以通过其它工具(如 cURL 或 Firefox 扩展 Poster 等)来创建 HTTP POST 请求来更改其它员工的工资。为了解决这个问题,需要对 raiseSalary的调用进行更加细粒度的控制。通过 Spring Security 提供的 AspectJ 支持就可以编写相关的控制逻辑,如 代码清单 7所示。
清单 7. 使用 AspectJ 进行细粒度的控制
private AspectJMethodSecurityInterceptor securityInterceptor;
private UserDao userDao;
pointcut salaryChange(): target(UserService)
&& execution(public void raiseSalary(..)) &&!within(SalaryManagementAspect);
Object around(): salaryChange() {
if (this.securityInterceptor == null) {
return proceed();
}
AspectJCallback callback = new AspectJCallback() {
public Object proceedWithObject() {
return proceed();
}
};
Object[] args = thisJoinPoint.getArgs();
String employee = (String) args[0]; // 要修改的员工的用户名
User user = userDao.getByUsername(employee);
String currentUser = UsernameHolder.getAuthenticatedUsername(); // 当前登录用户
if (!currentUser.equals(user.getManagerId())) {
throw new AccessDeniedException
("Only the direct manager can change the salary.");
}
return this.securityInterceptor.invoke(thisJoinPoint, callback);
}
}
如 代码清单 7所示,定义了一个切入点(pointcut)salaryChange和对应的环绕增强。当方法 raiseSalary被调用的时候,会比较要修改的员工的经理的用户名和当前登录用户的用户名是否一致。当不一致的时候就会抛出 AccessDeniedException异常。
在介绍了如何保护方法调用之后,下面介绍如何通过访问控制列表来保护领域对象。