6

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.

Laures
  • 5,389
  • 11
  • 50
  • 76
  • What version of Spring and Spring Security are You using? – Roadrunner Apr 12 '12 at 14:53
  • @Roadrunner spring 3.1.0.RELEASE on everything. – Laures Apr 12 '12 at 15:10
  • 1
    Can you debug if "PrePostAnnotationSecurityMetadataSource#getAttributes()" gets called for your method when Spring initializes ? – Luciano Apr 12 '12 at 18:44
  • @Luciano i can and it doesn't. it seems that because my PermissionEvaluator needs the channel service this service is created before the datasource (creation of the service is between loglines 215 (start) -702 (end); datasource is first mentioned in line 870 with "is not eligible for getting ....) – Laures Apr 13 '12 at 07:21
  • when i removed @inject ChannelService from my PermissionEvaluator the proxy was created. – Laures Apr 13 '12 at 07:29

2 Answers2

5

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.

Community
  • 1
  • 1
Roadrunner
  • 6,661
  • 1
  • 29
  • 38
  • in my PermissionEvaluator is use the Channel and User service because user permissions on these objects are store in these objects. But ´@inject´ of these beans causes these two services to be created before the PrePostAnnotationSecurityMetadataSource. when i remove them, my evaluator is called properly. now i just have to figure out how to inject my proxied services into the evaluator without causing this. – Laures Apr 13 '12 at 07:37
  • @Laures see my extended answer for more tips. – Roadrunner Apr 13 '12 at 10:14
  • the methods i use to check permissions are not secured (and wont be) but they are part of the service that has secured methods. i solved the problem by looking up the service object from the application context when i first need it. maybe not the cleanest solution but in my case there is no lower access level. – Laures Apr 13 '12 at 12:10
  • 2
    @Laures You should divide this service into 2 separete classes - one containing only not secured methods for use in security checks, and the other containing the secured methods for business use cases. Then both `@Inject` of unsecured service to Your `permissionEvaluator` and auto-proxying of secured one will work correctly. Even when not using secured methods, You're still using a class that You expect to be proxied with security checks - it's a dependency loop that should be removed by proposed division. – Roadrunner Apr 13 '12 at 20:14
  • The third update saved my life. You CANNOT call services in your customPermissionEvaluator that have any kind of Pre/PostAuthorization. This just fails silently and authorization is never executed. I solved it by adding @Lazy to the aforementioned services when passed through my PermissionEvaluator constructor – Whimusical Oct 15 '18 at 19:48
-1

it works, make sure that you have <sec:global-method-security pre-post-annotations="enabled"/> in your spring servlet (ie where you may have your <mvc:annotation-driven/>)

"sec" is from xmlns:sec="http://www.springframework.org/schema/security"

storm_buster
  • 7,362
  • 18
  • 53
  • 75
  • as you can see i already have the global-method-security tag configured and this is no web application, so no servlet or anything. – Laures Apr 11 '12 at 18:18
  • @Laures what is the Named annotation that you are using? it's not a spring annotation, maybe you should try with Component anntotation – storm_buster Apr 11 '12 at 19:21
  • named is a jsr330 annotation that basictly means the same this as component, but its standartized and not spring specific. jsr 330 is implemented by spring. – Laures Apr 11 '12 at 20:21