Spring #Preauthorize not working properly when provided with hierachical roles? - spring

I have a Spring-based application which defines a hierarchical set of roles for its users, like the following :
<bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ROOT > ROLE_SUDOER
ROLE_SUDOER > ROLE_USER
ROLE_USER > ROLE_DUMB
</value>
</property>
</bean>
so in my AccessDecisionManager, I am using 4 voters this way :
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
</bean>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</list>
</constructor-arg>
</bean>
<sec:global-method-security pre-post-annotations="enabled">
<sec:expression-handler ref="methodSecurityExpressionHandler"/>
</sec:global-method-security>
even though, I have configured this set of hierarchical roles, I am having an ACCESS DENIED exception when a user having the highest role, ROLE_ROOT, is trying to access the /myUrl url in the following controller:
#Controller
#PreAuthorize("hasRole('ROLE_SUDOER')")
#RequestMapping(value = "/myUrl")
public class BuggyController extends AbstractController {
#RequestMapping(value = "", method = RequestMethod.GET)
#ResponseBody
public List<SensitiveItem> getSensitiveItems(final Principal principal)
{
// removed for brievety
}
}
Here is the stacktrace (a part of it actually):
10:55:16.012 [ajp-bio-8029-exec-4] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - [ ] - Authorization successful
10:55:16.012 [ajp-bio-8029-exec-4] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - [ ] - RunAsManager did not change Authentication object
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - [ 192.168.xx.yyy] - Secure object: ReflectiveMethodInvocation: public java.util.List com.xxxx.yyyy.BuggyController.getSensitiveItems(java.security.Principal); target is of class [com.xxxx.yyyy.BuggyController.getSensitiveItems]; Attributes: [[authorize: 'hasRole('ROLE_SUDOER')', filter: 'null', filterTarget: 'null']]
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - [ 192.168.xx.yyy] - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#8bc6b2e4: Principal: org.springframework.security.core.userdetails.User#7a85da9c: Username: sudoer; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ROOT; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#2cd90: RemoteIpAddress: 192.168.22.112; SessionId: 8CE7068F50AD373ED2194B76A188BCEF; Granted Authorities: ROLE_ROOT
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.a.h.RoleHierarchyImpl - [ 192.168.xx.yyy] - getReachableGrantedAuthorities() - From the roles [ROLE_ROOT] one can reach [ROLE_ROOT, ROLE_SUDOER, ROLE_USER, ROLE_DUMB] in zero or more steps.
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.access.vote.AffirmativeBased - [ 192.168.xx.yyy] - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#65685e97, returned: -1
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.access.vote.AffirmativeBased - [ 192.168.xx.yyy] - Voter: org.springframework.security.access.vote.RoleVoter#29800e27, returned: 0
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.access.vote.AffirmativeBased - [ 192.168.xx.yyy] - Voter: org.springframework.security.access.vote.AuthenticatedVoter#162137ba, returned: 0
10:55:16.015 [ajp-bio-8029-exec-4] WARN c.d.d.utils.ExceptionResolver - [ 192.168.xx.yyy] - catched an exception: Access is denied
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
Logs clearly state that the ROLE_ROOT can escalate down to the ROLE_SUDOER:
10:55:16.013 [ajp-bio-8029-exec-4] DEBUG o.s.s.a.h.RoleHierarchyImpl - [ 192.168.xx.yyy] - getReachableGrantedAuthorities() - From the roles [ROLE_ROOT] one can reach [ROLE_ROOT, ROLE_SUDOER, ROLE_USER, ROLE_DUMB] in zero or more steps.
So my question is, if the ROLE_SUDOER is reachable from a higher priority role (here ROLE_ROOT), why is my call failing ?
In order to get it running, I had to explicitly add the highest role ROLE_ROOT as part of the method security control expression :
#Controller
#PreAuthorize("hasAnyRole('ROLE_ROOT', 'ROLE_SUDOER')")
#RequestMapping(value = "/myUrl")
public class BuggyController extends AbstractController {
#RequestMapping(value = "", method = RequestMethod.GET)
#ResponseBody
public List<SensitiveItem> getSensitiveItems(final Principal principal)
{
// removed for brievety
}
}
Which is really stupid since, it can become quite complicated if your hierarchical role set is huge : and that's exactly what hierarchical roles are for. It allows efficient role security control so that higher-priority roles can include lower priority ones.
Can someone help me on this issue ? Thanks in advance.

Related

Custom ConstraintValidator is called even if user role not listed in #PreAuthorize

I have a Spring Boot app with POST endpoint, #PreAuthorize role definition and a custom validator for the request body:
#PreAuthorize("hasAnyRole('ROLE_OTHER')")
#RequestMapping(value = "post", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public String post(#RequestBody #Valid DummyDTO dummyDTO) {
...
}
1) When I call the endpoint with wrong role and non-valid request body the custom validation code i.e. ConstraintValidator.isValid() is executed and I got 400 Bad Request.
2) When I call the endpoint with wrong role and valid request body the custom validation code i.e. ConstraintValidator.isValid() is executed and I got 403 Forbidden.
Why is the custom validation code executed before the #PreAuthorize check? I would expect to get 403 in both cases.
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : Successfully Authenticated: org.springframework.security.authentication.TestingAuthenticationToken#bbd5887f: Principal: key; Credentials: [PROTECTED]; Authenticated: false; Details: null; Granted Authorities: ROLE_USER
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#41ba74ef, returned: 1
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
DEBUG 12776 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /post reached end of additional filter chain; proceeding with original chain
INFO 12776 --- [nio-8080-exec-3] com.example.DummyValidator : > isValid(): [valid]
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.a.i.a.MethodSecurityInterceptor : Secure object: ReflectiveMethodInvocation: public java.lang.String com.example.MyController.post(com.example.DummyDTO); target is of class [com.example.MyController]; Attributes: [[authorize: 'hasAnyRole('ROLE_OTHER')', filter: 'null', filterTarget: 'null']]
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.TestingAuthenticationProvider
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.a.i.a.MethodSecurityInterceptor : Successfully Authenticated: org.springframework.security.authentication.TestingAuthenticationToken#bbd5887f: Principal: key; Credentials: [PROTECTED]; Authenticated: false; Details: null; Granted Authorities: ROLE_USER
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#356a19a2, returned: -1
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#7290b3e0, returned: 0
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#16f3e525, returned: 0
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is not anonymous); delegating to AccessDeniedHandler
org.springframework.security.access.AccessDeniedException: Access is denied
...
My DTO:
public class DummyDTO {
#DummyConstraint
private String name;
...
}
constraint:
#Documented
#Retention(RUNTIME)
#Target({TYPE, FIELD, PARAMETER, ANNOTATION_TYPE})
#Constraint(validatedBy = {DummyValidator.class})
public #interface DummyConstraint {
...
}
and validator:
public class DummyValidator implements ConstraintValidator<DummyConstraint, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(DummyValidator.class);
#Override
public void initialize(DummyConstraint constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
LOGGER.info("> isValid(): [{}]", value);
return "valid".equalsIgnoreCase(value);
}
}

Issue during Spring Security integration with Siteminder SSO integration

Spring's DispatcherServlet servlet sometimes calls GET request instead of POST request. It has to call POST method all the time as per JSP form method="post".
(DispatcherServlet.java:823) - DispatcherServlet with name 'springmvc' processing GET request for [/change.do]
All the configurations are correct. I checked and searched online. There is no correct answer for this.
I am using:
<spring.version>3.2.3.RELEASE</spring.version>
<spring.security.version>3.1.4.RELEASE</spring.security.version>
I am using lots of Spring filters and Tomcat 6.0.
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
Why Spring's DispatcherServlet servlet calls GET method instead of POST method occasionally? 90% of time it calls POST which is correct but 10% time it calls GET method for any of the forms which has method="post".
Tomcat debug log:
2017-01-27 11:30:08,811 [TP-Processor19] DEBUG (AntPathRequestMatcher.java:116) - Checking match of request : '/change.do'; against '/*.do'
2017-01-27 11:30:08,812 [TP-Processor19] DEBUG (AbstractSecurityInterceptor.java:194) - Secure object: FilterInvocation: URL: /change.do; Attributes: [IS_AUTHENTICATED_REMEMBERED]
2017-01-27 11:30:08,814 [TP-Processor19] DEBUG (AbstractSecurityInterceptor.java:310) - Previously Authenticated: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken#a24fd89f: Principal: ...#fdf65be0: Username: ABS; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Admin; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#59b2: RemoteIpAddress: 10.4.89.65; SessionId: null; Granted Authorities: Admin
2017-01-27 11:30:08,815 [TP-Processor19] DEBUG (AffirmativeBased.java:65) - Voter: org.springframework.security.access.vote.RoleVoter#4c019ea6, returned: 0
2017-01-27 11:30:08,817 [TP-Processor19] DEBUG (AffirmativeBased.java:65) - Voter: org.springframework.security.access.vote.AuthenticatedVoter#7e56cb45, returned: 1
2017-01-27 11:30:08,818 [TP-Processor19] DEBUG (AbstractSecurityInterceptor.java:215) - Authorization successful
2017-01-27 11:30:08,820 [TP-Processor19] DEBUG (AbstractSecurityInterceptor.java:227) - RunAsManager did not change Authentication object
2017-01-27 11:30:08,821 [TP-Processor19] DEBUG (FilterChainProxy.java:323) - /change.do reached end of additional filter chain; proceeding with original chain
2017-01-27 11:30:08,823 [TP-Processor19] DEBUG (DispatcherServlet.java:823) - DispatcherServlet with name 'springmvc' **processing GET request** for [/change.do]

Why do I still get an AuthenticationCredentialsNotFoundException?

I thought I had the solution to this but unfortunately the problem does still occur and I have no idea what else I could do.
What I do is login as a user. My AuthenticationProvider does not check anything which means that any user can login at the moment.
The thing is, that sometimes the login does work. I get a request through and load data from the server. Sometimes I have to wait a little while, 1 or 2 minutes, and all of a sudden I start receive the AuthenticationCredentialsNotFoundException. From time to time I can not login at all at the first time. I have to send another request in order to be able to successfully login.
I can't see a pattern or anything that would lead me to the cause of this. So, here I start with my LoginService and my implementation of the AuthenticationProvider:
public class LoginService {
private AuthenticationProvider adminAuthenticationProvider;
public LoginService(DSLContext ctx, AuthenticationProvider adminAuthenticationProvider) {
this.adminAuthenticationProvider = adminAuthenticationProvider;
}
#Transactional
public void login(String userId, String password) {
CustomUserDetails user = new CustomUserDetails(userId, password, true, true, true, true, new ArrayList<GrantedAuthority>());
Authentication auth = new UsernamePasswordAuthenticationToken(user, password,
new ArrayList<GrantedAuthority>());
try {
auth = this.adminAuthenticationProvider.authenticate(auth);
} catch(BadCredentialsException e) {
throw e;
}
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
public class AdminAuthenticationProvider implements AuthenticationProvider {
private RestaurantAdminRepository restaurantAdminRepository;
public AdminAuthenticationProvider(DSLContext ctx) {
this.restaurantAdminRepository = new RestaurantAdminRepository(ctx);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
List<String> roles = new ArrayList<>();
roles.add("ROLE_ADMIN");
Authentication customAuthentication = new CustomUserAuthentication(roles, authentication);
customAuthentication.setAuthenticated(true);
return customAuthentication;
}
#Override
public boolean supports(Class<? extends Object> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Nothing special about that I guess. My calls are secured just by isAuthenticated()
#PreAuthorize("isAuthenticated()")
public List<StoreDTO> getAvailableStores() {
// ..
return result;
}
The next thing is the debug output including the output of my own code and org.springframework on TRACE debug level. You can see that the authorization is successful but that after some requests the exception gets thrown. Sorry for this large output. You can also look at it here.
[http-bio-8080-exec-2] DEBUG com.mz.server.web.servlet.LoginServletImpl - Login request by userId: sfalk
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.LoginService - Login for sfalk
[http-bio-8080-exec-2] INFO com.mz.server.web.auth.AdminAuthenticationProvider - authenticate(), Username: sfalk
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - findByUsername(): sfalk
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:postgresql://localhost:5432/mz_db]
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - User found.
[http-bio-8080-exec-2] INFO com.mz.server.web.repository.StoreAdminRepository - Checking password for sfalk
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:postgresql://localhost:5432/mz_db]
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - Password valid.
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - Setting user com.mz.server.web.auth.CustomUserDetails#684666d: Username: sfalk; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities to 'authenticated'.
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.LoginService - User successfully authenticated [userId=sfalk]
[http-bio-8080-exec-2] DEBUG com.mz.server.web.servlet.StoreServletImpl - Requested available stores.
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.web.service.StoreService.getAvailableStores(); target is of class [com.mz.server.web.service.StoreService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - isAuthenticate(): true
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: com.mz.server.web.auth.CustomUserAuthentication#7d055aa6
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#36d4a51, returned: 1
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Authorization successful
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - RunAsManager did not change Authentication object
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.StoreService - Trying to get available stores for ..
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.StoreService - sfalk
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - Fetching stores for store_admin_id 1
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:postgresql://localhost:5432/mz_db]
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - Stores found..
[http-bio-8080-exec-2] DEBUG com.mz.server.web.servlet.StoreServletImpl - Requesting items for store ..
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.Map com.mz.server.web.service.StoreService.getItems(java.lang.Long); target is of class [com.mz.server.web.service.StoreService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - isAuthenticate(): true
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: com.mz.server.web.auth.CustomUserAuthentication#7d055aa6
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#36d4a51, returned: 1
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Authorization successful
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - RunAsManager did not change Authentication object
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.StoreService - Getting items.
[http-bio-8080-exec-2] DEBUG com.mz.server.web.repository.StoreAdminRepository - getItems
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:postgresql://localhost:5432/mz_db]
[http-bio-8080-exec-2] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
[http-bio-8080-exec-2] DEBUG com.mz.server.web.servlet.StoreServletImpl - Requested offers from 2016-01-11T00:00:00.278+01:00 to 2016-01-17T00:00:00.278+01:00.
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.web.service.StoreService.getUpcomingOffersForCalendarWeek(java.lang.Long,java.lang.String,java.lang.String); target is of class [com.mz.server.web.service.StoreService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - isAuthenticate(): true
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: com.mz.server.web.auth.CustomUserAuthentication#7d055aa6
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#36d4a51, returned: 1
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Authorization successful
[http-bio-8080-exec-2] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - RunAsManager did not change Authentication object
[http-bio-8080-exec-2] DEBUG com.mz.server.web.service.StoreService - Getting offers ..
[http-bio-8080-exec-2] DEBUG com.mz.server.web.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-1] DEBUG com.mz.server.web.servlet.StoreServletImpl - Requested offers from 2016-01-11T00:00:00.167+01:00 to 2016-01-17T00:00:00.167+01:00.
[http-bio-8080-exec-1] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.web.service.StoreService.getUpcomingOffersForCalendarWeek(java.lang.Long,java.lang.String,java.lang.String); target is of class [com.mz.server.web.service.StoreService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-1] TRACE org.springframework.web.context.support.XmlWebApplicationContext - Publishing event in Root WebApplicationContext: org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent[source=ReflectiveMethodInvocation: public java.util.List com.mz.server.web.service.StoreService.getUpcomingOffersForCalendarWeek(java.lang.Long,java.lang.String,java.lang.String); target is of class [com.mz.server.web.service.StoreService]]
[http-bio-8080-exec-1] DEBUG com.mz.server.web.auth.CustomHttpSessionListener - AuthenticationCredentialsNotFoundEvent
Jän 12, 2016 11:27:02 PM org.apache.catalina.core.ApplicationContext log
SEVERE: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.util.List com.mz.shared.web.service.store.StoreServlet.getUpcomingOffersForCalendarWeek(java.lang.Long,java.lang.String,java.lang.String)' threw an unexpected exception: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:416)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:605)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:333)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:303)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:373)
at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:378)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:222)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at com.mz.server.web.service.StoreService$$EnhancerBySpringCGLIB$$b5728734.getUpcomingOffersForCalendarWeek(<generated>)
at com.mz.server.web.servlet.StoreServletImpl.getUpcomingOffersForCalendarWeek(StoreServletImpl.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:587)
... 25 more
The last thing are my application context configuration files. This is my configuration applicationContext-spring-acl.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd">
<!-- Imports -->
<import resource="applicationContext-jooq.xml"/>
<!-- See 15.3.2 Built-In Expression #http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html#el-permission-evaluator -->
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<!-- To use hasPermission() in expressions, configure a PermissionEvaluator -->
<property name="permissionEvaluator" ref="permissionEvaluator" />
<property name="roleHierarchy" ref="roleHierarchy" />
</bean>
<bean class="com.mahlzeit.server.web.auth.permission.CustomAclPermissionEvaluator" id="permissionEvaluator">
<constructor-arg ref="aclService" />
</bean>
<!-- Declare an acl service -->
<bean class="org.springframework.security.acls.jdbc.JdbcMutableAclService" id="aclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
</bean>
<!-- Declare a lookup strategy -->
<bean id="lookupStrategy"
class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="auditLogger" />
</bean>
<!-- Declare an acl cache -->
<bean id="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true"/>
</property>
<property name="cacheName" value="aclCache" />
</bean>
</constructor-arg>
<constructor-arg>
<bean
class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean
class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean
class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ACL_ADMIN" />
</bean>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<!-- Declare an acl authorization strategy -->
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean
class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN" />
</bean>
<bean
class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN" />
</bean>
<bean
class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN" />
</bean>
</list>
</constructor-arg>
</bean>
<!-- Declare an audit logger -->
<bean id="auditLogger"
class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
<!-- http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.html -->
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_USER
ROLE_USER > ROLE_VISITOR
</value>
</property>
</bean>
<sec:global-method-security authentication-manager-ref="authenticationManager" pre-post-annotations="enabled">
<sec:expression-handler ref="expressionHandler"/>
</sec:global-method-security>
</beans>
And this is applicationContext-spring-security.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd"
>
<!-- Imports -->
<import resource="applicationContext-spring-acl.xml"/>
<sec:http pattern="/**" auto-config="true" use-expressions="true"/>
<bean id="httpSessionSecurityContextRepository" class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
<property name='allowSessionCreation' value='false' />
</bean>
<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<constructor-arg ref="httpSessionSecurityContextRepository" />
</bean>
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<sec:filter-chain pattern="/**" filters="securityContextPersistenceFilter" />
</list>
</constructor-arg>
</bean>
<bean id="authenticationListener" class="com.mahlzeit.server.web.auth.CustomAuthenticationListener"/>
<bean id="adminAuthenticationProvider" class="com.mahlzeit.server.web.auth.AdminAuthenticationProvider">
<constructor-arg ref="dslContext" />
</bean>
<bean id="userDetailsService" class="com.mahlzeit.server.web.service.CustomUserDetailsService"/>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="adminAuthenticationProvider"/>
</sec:authentication-manager>
</beans>
Thank you for any help that sheds some light on this.
It seems to me that the SecurityContextPersistenceFilter doesn't execute around your requests. I can see it is defined in your applicationContext-spring-security.xml but, since you didn't post your web.xml, I can only assume you don't have the corresponding filter entry in your web.xml with DelegatingFilterProxy as a filter class. You can define the filter in the web.xml like this:
<filter>
<filter-name>filterChainProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>filterChainProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note that the filter-name references a bean from your spring context with the name filterChainProxy.
From the DelegatingFilterProxy javadoc:
Proxy for a standard Servlet 2.3 Filter, delegating to a
Spring-managed bean that implements the Filter interface. Supports a
"targetBeanName" filter init-param in web.xml, specifying the name of
the target bean in the Spring application context.
web.xml will usually contain a DelegatingFilterProxy definition, with
the specified filter-name corresponding to a bean name in Spring's
root application context. All calls to the filter proxy will then be
delegated to that bean in the Spring context, which is required to
implement the standard Servlet 2.3 Filter interface.
I hope this helps.
I wonder if the issue is related to your empty GrantedAuthority List in the login method.
In my implementation LocalAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider and in the GrantedAuthority list I add user role that comes from my domain.
final List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
for(com.tony.trip.domain.Role role:user.getRoles()){
auths.add(new SimpleGrantedAuthority(role.getRolename()));
}
Hope this hepls

loadUserByUsername being passed empty username

I am trying to implement an example from "Pro Spring Security" by Scarioni which implements a custom in memory user model (implementing the UserDetailsService interface) and custom expression handler. When I try to log in, the loadUserByUsername() method of my CustomInMemoryUserDetailsManager is passed a blank (not null) string for the username. This results in access denied. If I force the username to be that expected (admin), everything works fine including the custom expression handling.
Here is my security configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<security:http auto-config="true" use-expressions="true" >
<security:expression-handler ref="expressionHandler" />
<security:intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN') and hasIpAddress('127.0.0.1') and over18"/>
<security:remember-me key="terror-key" />
<security:form-login login-page="/custom_login"
authentication-failure-handler-ref="serverErrorHandler"
username-parameter="user_param" password-parameter="pass_param" />
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="inMemoryUserServiceWithCustomUser" />
</security:authentication-manager>
<!-- Custom expression handler bean -->
<bean id="expressionHandler" class="com.apress.pss.terrormovies.security.CustomWebSecurityExpressionHandler"/>
<bean id="inMemoryUserServiceWithCustomUser"
class="com.apress.pss.terrormovies.spring.CustomInMemoryUserDetailsManager">
<constructor-arg>
<list>
<bean class="com.apress.pss.terrormovies.model.User">
<constructor-arg value="admin"/>
<constructor-arg value="admin"/>
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN"/>
</bean>
</list>
</constructor-arg>
<constructor-arg value="Scarioni"/>
<constructor-arg value="19"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="logoutRedirectToAny"
class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
</bean>
<bean id="serverErrorHandler" class="com.apress.pss.terrormovies.security.ServerErrorFailureHandler"/>
</beans>
Here is my CustomInMemoryUserDetailsManager class:
package com.apress.pss.terrormovies.spring;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.apress.pss.terrormovies.model.User;
public class CustomInMemoryUserDetailsManager implements UserDetailsService {
private final Log logger = LogFactory.getLog(getClass());
private Map<String, User> users = new HashMap<String, User>();
public CustomInMemoryUserDetailsManager(Collection<User> users) {
for (User user : users) {
this.users.put(user.getUsername().toLowerCase(), user);
logger.debug("CustomInMemoryUserDetailsManager()- put username: " +
user.getUsername() + " last name: " + user.getLastName() + " authority: " +
user.getAuthorities());
}
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Test - force user name to that expected
//username = "admin";
if (username.equals("")) logger.debug("loadUserByUsername()- username is blank!!!");
logger.debug("loadUserByUsername()- username: " + username);
User user = users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
}
logger.debug("loadUserByUsername()- found " + user.getUsername());
User userNew = new User(user.getUsername(), user.getPassword(),
user.getAuthorities(), user.getLastName(), user.getAge());
return userNew;
}
}
I turned on Spring debugging and got a very large log, here is what I think is the relevent part from the point of logging to j_spring_security_check:
:28,785 DEBUG main DispatcherServlet:130 - Servlet 'terrormovies' configured successfully
08:29:48,058 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 1 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
08:29:48,072 DEBUG qtp1624348237-15 HttpSessionSecurityContextRepository:127 - No HttpSession currently exists
08:29:48,085 DEBUG qtp1624348237-15 HttpSessionSecurityContextRepository:85 - No SecurityContext was available from the HttpSession: null. A new one will be created.
08:29:48,120 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 2 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
08:29:48,120 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 3 of 11 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
08:29:48,120 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 4 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
08:29:48,121 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 5 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
08:29:48,121 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 6 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
08:29:48,122 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 7 of 11 in additional filter chain; firing Filter: 'RememberMeAuthenticationFilter'
08:29:48,123 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
08:29:48,124 DEBUG qtp1624348237-15 AnonymousAuthenticationFilter:102 - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
08:29:48,125 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
08:29:48,125 DEBUG qtp1624348237-15 SessionManagementFilter:92 - Requested session ID ncic677387xfiq2ciohmau1 is invalid.
08:29:48,126 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
08:29:48,126 DEBUG qtp1624348237-15 FilterChainProxy:337 - /admin/movies at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
08:29:48,126 DEBUG qtp1624348237-15 AntPathRequestMatcher:103 - Checking match of request : '/admin/movies'; against '/admin/*'
08:29:48,127 DEBUG qtp1624348237-15 FilterSecurityInterceptor:194 - Secure object: FilterInvocation: URL: /admin/movies; Attributes: [hasRole('ROLE_ADMIN') and hasIpAddress('127.0.0.1') and over18]
08:29:48,127 DEBUG qtp1624348237-15 FilterSecurityInterceptor:310 - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
08:29:48,129 DEBUG qtp1624348237-15 CustomWebSecurityExpressionRoot:22 - CustomWebSecurityExpressionRoot()- call
08:29:48,154 DEBUG qtp1624348237-15 AffirmativeBased:65 - Voter: org.springframework.security.web.access.expression.WebExpressionVoter#35333295, returned: -1
08:29:48,157 DEBUG qtp1624348237-15 ExceptionTranslationFilter:165 - Access is denied(user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
...
On initialization you can see the user being correctly entered into the users HashMap in the log:
08:29:25,912 DEBUG main CustomInMemoryUserDetailsManager:26 - CustomInMemoryUserDetailsManager()- put username: admin last name: Scarioni authority: [ROLE_ADMIN]
I noticed the user is coming in as anonymous, I'm not sure why. Any help would be greatly appreciated.
Thanks
mike

PreAuthorize doesn't work

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.
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.
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"

Resources