Чтобы программно внедрить функциональность Spring Security в существующие bean-компоненты, вам может понадобиться использовать контекст приложения Spring Security и зарегистрировать ваши bean-компоненты там:
@Test
public void testSpringSecurity() throws Exception {
InMemoryXmlApplicationContext ctx = initSpringAndSpringSecurity();
// Creates new instance
IMyService secured = (IMyService) ctx.getAutowireCapableBeanFactory()
.initializeBean(new MyService(), "myService");
assertTrue(AopUtils.isAopProxy(secured));
fakeSecurityContext("ROLE_USER");
secured.getCustomers(); // Works: @Secured("ROLE_USER")
fakeSecurityContext("ROLE_FOO");
try {
secured.getCustomers(); // Throws AccessDenied Exception
fail("AccessDeniedException expected");
} catch (AccessDeniedException expected) {
}
}
private InMemoryXmlApplicationContext initSpringAndSpringSecurity() {
InMemoryXmlApplicationContext ctx = new InMemoryXmlApplicationContext(
"<b:bean id='authenticationManager' class='org.springframework.security.MockAuthenticationManager' /> "
+ "<b:bean id='accessDecisionManager' class='org.springframework.security.vote.UnanimousBased'><b:property name='decisionVoters'><b:list><b:bean class='org.springframework.security.vote.RoleVoter'/></b:list></b:property></b:bean>"
+ "<b:bean id='objectDefinitionSource' class='org.springframework.security.annotation.SecuredMethodDefinitionSource' /> "
+ "<b:bean id='autoproxy' class='org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator'/>"
+ "<b:bean id='methodSecurityAdvisor' class='org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor' autowire='constructor'/>"
+ "<b:bean id='securityInterceptor' class='org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor'><b:property name='authenticationManager' ref='authenticationManager' /><b:property name='accessDecisionManager' ref='accessDecisionManager' /><b:property name='objectDefinitionSource' ref='objectDefinitionSource' /></b:bean>");
return ctx;
}
Я использовал контекст приложения в памяти, поскольку MethodDefinitionSourceAdvisor
в своей документации заявляет, что автоматическое проксирование включено только для ApplicationContext
s. Таким образом, я считаю, что вам нужен контекст приложения для автоматического проксирования, если вы не используете отдельный ProxyFactoryBean
для каждого объекта службы. Но так как вы используете аннотацию @Secured
, я подозреваю, что это то же самое, что и авто-проксирование.
fakeSecurityContext()
просто установил объект Authentication
с данной ролью в SecurityContextHolder
для целей тестирования.
Вы можете сделать это с функциональностью Spring Core самостоятельно. Предположим, у вас есть служба, которая возвращает список клиентов, и текущий пользователь может просматривать только клиентов с определенным пределом дохода. Следующий тест будет нашим стартом:
@Test
public void testSecurity() throws Exception {
ClassPathXmlApplicationContext appCtx = new ClassPathXmlApplicationContext(
"spring.xml");
IMyService service = (IMyService) appCtx.getBean("secured",
IMyService.class);
assertEquals(1, service.getCustomers().size());
}
Это оригинальная реализация сервиса:
public class MyService implements IMyService {
public List<Customer> getCustomers() {
return Arrays.asList(new Customer(100000), new Customer(5000));
}
}
Сконфигурируйте ваш сервисный объект в spring.xml
и добавьте метод-перехватчик:
<bean id="service" class="de.mhaller.spring.MyService"></bean>
<bean id="securityInterceptor" class="de.mhaller.spring.MyServiceInterceptor">
<property name="revenueLimit" value="50000"/>
</bean>
<bean id="secured" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetName" value="service" />
<property name="interceptorNames">
<list>
<value>securityInterceptor</value>
</list>
</property>
</bean>
Реализация перехватчика безопасности:
public class MyServiceInterceptor implements MethodInterceptor {
private int revenueLimit = 10000;
public void setRevenueLimit(int revenueLimit) {
this.revenueLimit = revenueLimit;
}
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation mi) throws Throwable {
List<Customer> filtered = new ArrayList<Customer>();
List<Customer> result = (List<Customer>) mi.proceed();
for (Customer customer : result) {
if (customer.isRevenueBelow(revenueLimit)) {
filtered.add(customer);
}
}
return filtered;
}
}
Преимущество использования такого подхода состоит в том, что вы можете не только декларативно проверять роли текущего пользователя, но и динамически применять корпоративные политики, например, ограничить возвращаемые объекты на основе бизнес-ценностей.