近日在使用SpringSecurity的项目中发现一个小问题,就是在接口上加了@Secured标注限制调用接口权限时,某些JUnit无法正常调用了。
例如:
@Secured(PrivilegeDAO.ROLE_REMIND_CREATE)
public Serializable save(Users user) throws BusinessException;
调用save方法时,必须具备 PrivilegeDAO.ROLE_REMIND_CREATE权限才行。
原因很简单,执行JUnit的时候并没有经过SpringSecurity的登录把资源设置到上下文,所以是无登录权限的。而调用接口的时候又要获取执行权限,这当然造成无法调用的局面了。
花了点时间,查找资源,找到个解决方案,分享下。其实就是覆盖原来认真配置信息,然后把供单元测试用的TestingAuthenticationToken设置回spring容器当中。所以之后执行单元测试时就有权限了。注意构造TestingAuthenticationToken的时候必须根据项目具体情况来添加GrantedAuthority[]。
具体代码参考如下:
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import org.springframework.beans.factory.BeanFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.TestingAuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import cn.com.timekey.drugmonitor.po.Privilege; /**
* @author kennylee
*
*/
public class SecurityTestingUnit { /**
* 设置SpringSecurity的登录用户权限。
*
* @param ctx
* @param auths
*/
public static void setAuthToken(BeanFactory ctx, GrantedAuthority[] auths) {
TestingAuthenticationToken token = new TestingAuthenticationToken(
"admin", "test", auths);
// Override the regular spring configuration
ProviderManager providerManager = (ProviderManager) ctx
.getBean("authenticationManager");
List<AuthenticationProvider> list = new ArrayList<AuthenticationProvider>();
TestingAuthenticationProvider testingAuthenticationProvider = new TestingAuthenticationProvider();
list.add(testingAuthenticationProvider);
providerManager.setProviders(list); // Create and store the SpringSecurity SecureContext into the
// SecurityContextHolder.
SecurityContextImpl secureContext = new SecurityContextImpl();
secureContext.setAuthentication(token);
SecurityContextHolder.setContext(secureContext);
} /**
* 构造权限组
*
* @param rolePrivileges
* @return
*/
public static GrantedAuthority[] generateAuthorities(
Collection<Privilege> privileges) {
GrantedAuthority[] auths = new GrantedAuthority[privileges.size()];
if (!privileges.isEmpty()) {
int count = 0;
for (Privilege rolePrivilege : privileges) {
String privilegeName = rolePrivilege.getPrivilegeName();
GrantedAuthority authority = new GrantedAuthorityImpl(
privilegeName);
auths[count] = authority;
count++;
}
}
return auths;
} }
其中generateAuthorities只是我的一个辅助构造方法。不是重点内容。setAuthToken为主体代码。
JUnit testing with Acegi Security Here's a tip if you are trying to do some unit testing with Acegi Security - particularly if you are doing role based authorization of method calls on your manager objects via interception. Basically, a secure method interceptor will a) need an authentication token to play with, and b) a way to find out what authorities the user has. We need to cater for this when running the tests. Acegi Security conveniently provides a TestingAuthenticationToken for unit testing purposes so we are Ok with the token part. You will now need to provide a suitable authentication provider. Somewhat unsurprisingly, there is a TestingAuthenticationProvider that you can add to your Spring config file to accommodate this. But here is the problem: we already have this configured for production using a different provider (obviously), so we would have to resort to changing the config files depending on if you are running tests or not. This is too fragile for me, so here is the essence of the tip: leave the configuration file alone and dynamically changes the provider in the setup of the unit test. Here is some code that I have in my JUnit setUp() method: // Grant all roles to noddy.
TestingAuthenticationToken token = new TestingAuthenticationToken(
"noddy", "test", new GrantedAuthority[] {
new GrantedAuthorityImpl("User"),
new GrantedAuthorityImpl("Administrator") }); // Override the regular spring configuration
ProviderManager providerManager = (ProviderManager) ctx.getBean("authenticationManager");
List list = new ArrayList();
list.add(new TestingAuthenticationProvider());
providerManager.setProviders(list); // Create and store the Acegi SecureContext into the ContextHolder.
SecureContextImpl secureContext = new SecureContextImpl();
secureContext.setAuthentication(token);
ContextHolder.setContext(secureContext);
So... it sets up an authentication token, then resets the authentication managers' provider list with one that contains the TestingAuthenticationProvider. It then creates the context for the method execution. The problem with this is it does it for every single test method - that is how JUnit works for better or worse. Hope it proves useful for you.
参考见:http://fishdujour.typepad.com/blog/2005/02/junit_testing_w.html