Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Secured function getting Access Denied for authorized user

I've followed quite a few threads to implement Spring Security to my rest API. Initially I get stuck at @Secured annotation being ignored, now that I got that resolved, I am stuck at getting access denied.

Feels like my problem sound very similar to: @secured with granted authorities throws access denied exception - but I am still getting access denied.

Here's my setup:

spring-security.xml

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService">
        <password-encoder ref="passwordEncoder" />
    </authentication-provider>
</authentication-manager>

<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.PlaintextPasswordEncoder"/>

<user-service id="userDetailsService">
    <user name="john" password="john1" authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="jane" password="jane1" authorities="ROLE_USER" />
    <user name="apiuser" password="apiuser" authorities="PERMISSION_TEST" />
</user-service>

Controller:

@Controller
@RequestMapping("/secure")
public class SecureController
{
    private static final Logger logger = Logger.getLogger(SecureController.class);

    @Secured("PERMISSION_TEST")
    @RequestMapping(value = "/makeRequest", method = RequestMethod.GET)
    @ResponseBody
    public SimpleDTO executeSecureCall()
    {
        logger.debug("[executeSecureCall] Received request to a secure method");

        SimpleDTO dto = new SimpleDTO();
        dto.setStringVariable("You are authorized!");

        return dto;
    }

}

Now - without the proper

<security:global-method-security secured-annotations="enabled"/>

My request goes through (this is because the @Secured annotation is ignored). When I put it in and accessing it using "apiuser"/"apiuser", I kept getting access denied, the debug log:

11:42:43,899 [http-apr-8080-exec-4] DEBUG MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@cc12af5d: Principal: org.springframework.security.core.userdetails.User@d059c8e5: Username: apiuser; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: PERMISSION_TEST; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: PERMISSION_TEST

11:42:43,899 [http-apr-8080-exec-4] DEBUG AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter@2a9a42ef, returned: 0
11:42:43,900 [http-apr-8080-exec-4] DEBUG AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter@75a06ec2, returned: 0

11:42:43,902 [http-apr-8080-exec-4] DEBUG AnnotationMethodHandlerExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController@342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,905 [http-apr-8080-exec-4] DEBUG ResponseStatusExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController@342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,906 [http-apr-8080-exec-4] DEBUG DefaultHandlerExceptionResolver - Resolving exception from handler [com.test.webapp.spring.controller.SecureController@342d150f]: org.springframework.security.access.AccessDeniedException: Access is denied
11:42:43,909 [http-apr-8080-exec-4] DEBUG DispatcherServlet - Could not complete request
org.springframework.security.access.AccessDeniedException: Access is denied

Thoughts?

Thanks in advance!

like image 758
TS- Avatar asked Mar 04 '13 17:03

TS-


2 Answers

As I remember @Secured annotation works only with role names starting ROLE_ by default.

You may switch to @PreAuthorize("hasAuthority('PERMISSION_TEST')") (with pre-post-annotations="enabled") or rename your role.

like image 85
Michail Nikolaev Avatar answered Nov 09 '22 23:11

Michail Nikolaev


I want to add a little more to Michail Nikolaev answer. My answer is from the source code point of view. I want you to understand why access was denied.

From documentation:

When you use a namespace configuration, a default instance of AccessDecisionManager is automatically registered for you and will be used for making access decisions for method invocations and web URL access, based on the access attributes you specify in your intercept-url and protect-pointcut declarations (and in annotations if you are using annotation secured methods). The default strategy is to use an AffirmativeBased AccessDecisionManager with a RoleVoter and an AuthenticatedVoter.

RoleVoter uses ROLE_ prefix (by default) in order to decide if it can vote. You can change that default prefix with RoleVoter.setRolePrefix() method.

From source code:

public class RoleVoter implements AccessDecisionVoter<Object> {

(...)

private String rolePrefix = "ROLE_";

(...)

public void setRolePrefix(String rolePrefix) {

   this.rolePrefix = rolePrefix;

}

(...)

public boolean supports(ConfigAttribute attribute) {

   if ((attribute.getAttribute() != null) &&
              attribute.getAttribute().startsWith(getRolePrefix())) {
       return true;
   } else {
       return false;
   }
}

(...)

public int vote(Authentication authentication, Object object, 
                       Collection<ConfigAttribute> attributes) {
    int result = ACCESS_ABSTAIN;
    Collection<? extends GrantedAuthority> authorities = 
                                            extractAuthorities(authentication);

    for (ConfigAttribute attribute : attributes) {
        if (this.supports(attribute)) {
            result = ACCESS_DENIED;

            // Attempt to find a matching granted authority
            for (GrantedAuthority authority : authorities) {
                if (attribute.getAttribute().equals(authority.getAuthority())) {
                    return ACCESS_GRANTED;
                }
            }
        }
    }

    return result;
}

PERMISSION_TEST doesn't start with ROLE_ so RoleVoter abstains from deciding. AuthenticatedVoter abstains too (as you have not used IS_AUTHENTICATED_ prefix in @Secured annotation).

Finally, AffirmativeBased implementation of AccessDecisionManager throws AccessDeniedException because both AccessDecisionVoters abstained from voting.

Java docs for AffirmativeBased:

Simple concrete implementation of org.springframework.security.access.AccessDecisionManager that grants access if any AccessDecisionVoter returns an affirmative response.

like image 8
Maciej Ziarko Avatar answered Nov 09 '22 22:11

Maciej Ziarko