I'm writing a socket server (no web-application !) application and want to use method-based security to handle my ACL needs. i followed a small tutorial i found spring security by example
so far i configured:
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler" />
</security:global-method-security>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean id="permissionEvaluator" class="myPermissionEvaluator" />
</property>
</bean>
<security:authentication-manager id="authenticationmanager">
<security:authentication-provider ref="authenticationprovider" />
</security:authentication-manager>
<bean id="authenticationprovider" class="myAuthenticationProvider" />
With a service bean:
@Named
public class ChannelService {
@PreAuthorize("isAuthenticated() and hasPermission(#channel, 'CHANNEL_WRITE')")
public void writeMessage(Channel channel, String message) { ... }
}
Everything compiles and the application starts and works fine, but without access control. My debug log shows that my Evaluator is never called.
When i tried something similar with a @Secured
annotation the annotation was evaluated and access was denied. but simple role based security isn't enough for my requirements.
EDIT did some more tests: when i configure only secured-annotations="enabled" the role based security works. when configure pre-post-annotations="enabled" in ADDITION neither secured nor preauthorize works. when i configure only pre-post-annotations it still doesn't work.
EDIT2
some more tests: with only secured_annotations="enabled" the call to my channelservice goes through the Cglib2AopProxy as soon as i activate pre-post-annotations the call lands directly in the channelservice. no interceptor, no proxy, nothing.
I'm getting kind of desperate...
EDIT3
I debug-logged my testruns here is the part for spring-security
with only secured-annotations="enabled"
2012-04-12 13:36:46,171 INFO [main] o.s.s.c.SpringSecurityCoreVersion - You are running with Spring Security Core 3.1.0.RELEASE
2012-04-12 13:36:46,174 INFO [main] o.s.s.c.SecurityNamespaceHandler - Spring Security 'config' module version is 3.1.0.RELEASE
2012-04-12 13:36:49,042 DEBUG [main] o.s.s.a.m.DelegatingMethodSecurityMetadataSource - Caching method [CacheKey[mystuff.UserService; public void mystuff.UserService.serverBan(java.lang.String,mystuff.models.User,org.joda.time.DateTime)]] with attributes [user]
2012-04-12 13:36:49,138 DEBUG [main] o.s.s.a.i.a.MethodSecurityInterceptor - Validated configuration attributes
2012-04-12 13:36:49,221 DEBUG [main] o.s.s.a.m.DelegatingMethodSecurityMetadataSource - Caching method [CacheKey[mystuff.ChannelService; public void mystuff.ChannelService.writeMessage(mystuff.models.Channel,java.lang.String)]] with attributes [blubb]
2012-04-12 13:36:51,159 DEBUG [main] o.s.s.a.ProviderManager - Authentication attempt using mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:36:56,166 DEBUG [Timer-1] o.s.s.a.ProviderManager - Authentication attempt using mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:36:56,183 DEBUG [Timer-1] o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public void mystuff.ChannelService.writeMessage(mystuff.models.Channel,java.lang.String); target is of class [mystuff.ChannelService]; Attributes: [blubb]
2012-04-12 13:36:56,184 DEBUG [Timer-1] o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@312e8aef: Principal: mystuff.UserId@ced1752b; Credentials: [PROTECTED]; Authenticated: true; Details: null; Not granted any authorities
Exception in thread "Timer-1" org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AbstractAccessDecisionManager.checkAllowIfAllAbstainDecisions(AbstractAccessDecisionManager.java:70)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:88)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:205)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at mystuff.ChannelService$$EnhancerByCGLIB$$3ad5e57f.writeMessage(<generated>)
at mystuff.run(DataGenerator.java:109)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
2012-04-12 13:36:56,185 DEBUG [Timer-1] o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter@1cfe174, returned: 0
2012-04-12 13:36:56,185 DEBUG [Timer-1] o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter@da89a7, returned: 0
with pre-post-annotations="enabled"
2012-04-12 13:39:54,926 INFO [main] o.s.s.c.SpringSecurityCoreVersion - You are running with Spring Security Core 3.1.0.RELEASE
2012-04-12 13:39:54,929 INFO [main] o.s.s.c.SecurityNamespaceHandler - Spring Security 'config' module version is 3.1.0.RELEASE
2012-04-12 13:39:54,989 INFO [main] o.s.s.c.m.GlobalMethodSecurityBeanDefinitionParser - Using bean 'expressionHandler' as method ExpressionHandler implementation
2012-04-12 13:39:59,812 DEBUG [main] o.s.s.a.ProviderManager - Authentication attempt mystuff.GlobalchatAuthenticationProvider
2012-04-12 13:39:59,850 DEBUG [main] o.s.s.a.i.a.MethodSecurityInterceptor - Validated configuration attributes
As far as i understand this log output spring doesn't realize my beans need to be proxied, so they aren't and so i don't get security.
EDIT4
I debug-logged the complete sprint startup... (thats one big log) and there i find:
2012-04-12 14:40:41,385 INFO [main] o.s.c.s.ClassPathXmlApplicationContext - Bean 'channelService' of type [class mystuff.ChannelService] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
is there a way to figure out why? because as far as i understand it. because of @preauthorize the bean should be eligible. with only secured-annotations="enabled" i get a post processing log.
In the case of the @PreAuthorize annotation this is done by the MethodSecurityInterceptor . What the MethodSecurityInterceptor does is, based on the @PreAuthorize annotation and the expression written in there (the expression is a SpEL expression) determine if the current user is authorized to access the method.
The @PreAuthorize authorizes on the basis of role or the argument which is passed to the method. The @PostAuthorize checks for authrorisation after method execution. The @PostAuthorize authorizes on the basis of logged in roles, return object by method and passed argument to the method.
Method-level security is implemented by placing the @PreAuthorize annotation on controller methods (actually one of a set of annotations available, but the most commonly used). This annotation contains a Spring Expression Language (SpEL) snippet that is assessed to determine if the request should be authenticated.
The @PreAuthorize annotation checks the given expression before entering the method, whereas the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.
This configuration worked just as expected for me:
<bean id="securityExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" />
<bean id="preInvocationAdvice"
class="org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice"
p:expressionHandler-ref="securityExpressionHandler" />
<util:list id="decisionVoters">
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean class="org.springframework.security.access.vote.RoleVoter" />
<bean class="org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter"
c:pre-ref="preInvocationAdvice" />
</util:list>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased"
c:decisionVoters-ref="decisionVoters" />
<sec:global-method-security
authentication-manager-ref="authenticationManager"
access-decision-manager-ref="accessDecisionManager"
pre-post-annotations="enabled" />
I got the log message:
WARN org.springframework.security.access.expression.DenyAllPermissionEvaluator -
Denying user jack permission 'CHANNEL_WRITE' on object Channel[ name=null ]
And an exception:
org.springframework.security.access.AccessDeniedException: Access is denied
From a simple test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:META-INF/spring/application-context.xml")
public class SpringSecurityPrePostTest {
@Autowired
ChannelService channelService;
@Test
public void shouldSecureService() throws Exception {
Authentication authentication = new UsernamePasswordAuthenticationToken("jack", "sparrow");
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
channelService.writeMessage(new Channel(), "test");
}
}
One thing I did diffrent was to use interface on a service and JDK proxies instead of cglib:
public interface ChannelService {
void writeMessage(Channel channel, String message);
}
and:
@Component
public class ChannelServiceImpl implements ChannelService {
private static final Logger LOG = LoggerFactory.getLogger(ChannelServiceImpl.class);
@Override
@PreAuthorize("isAuthenticated() and hasPermission(#channel, 'CHANNEL_WRITE')")
public void writeMessage(Channel channel, String message) {
LOG.info("Writing message {} to: {}" , message, channel);
}
}
UPDATE1:
With this simplified config I get the same result:
<bean id="securityExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" />
<sec:global-method-security
authentication-manager-ref="authenticationManager"
pre-post-annotations="enabled">
<sec:expression-handler ref="securityExpressionHandler" />
</sec:global-method-security>
UPDATE2:
The debug message from Edit4 indicates that channelService
may not have bean proxied at all as it got classified as not eligible for auto-proxying
. This qiestion answers similar problem - try not to use @Autowired
or any other mechanism based on BeanPostProcessors
to set up the beans involved in security checks (i.e. myPermissionEvaluator
).
UPDATE3:
You cannot use secured resources (i.e. services) within beans responsible for security checks! This creates a dependency loop and is a error in Your configuration. You must use lover level access (i.e. DAO) to check permissions, anything that is not secured! Implementing security checks using secured resources is not what You want to do.
If despite using not secured resources with @Autowired
things don't work as expected, try using old-school XML confiuration style for all beans involved in security checks. Also remember that <context:component-scan />
is in fact a BeanDefinitionRegistryPostProcessor
and introduces the scanned beans into the BeanFactory
after all the ones declared in XML are already there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With