Saturday, March 19, 2016

Access denied exception while trying to access the login page in spring security

Leave a Comment

I am using java based spring security. I have created the custom access decision voter impl.

But when I run the application, I can not open the login page as it says, access is denied.

This happened after I added the custom access decision voter impl. I guess the issue is because of the following code in custom AccessDecisionVoter.

if(authentication instanceof AnonymousAuthenticationToken)             return ACCESS_DENIED; 

But i need this so that permissions are not checked for not logged in users.

And it goes in infinite loop, login page, access decision voter, access denied, login page and so on.

Below is the spring security configuration code.

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {      @Autowired     private UserDetailsService userDetailsService;      @Autowired     private AffirmativeBased accessDecisionManager;      @Bean     @Autowired     public AffirmativeBased accessDecisionManager(AccessDecisionVoterImpl accessDecisionVoter) {         List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<AccessDecisionVoter<?>>();         accessDecisionVoters.add(accessDecisionVoter);         AffirmativeBased accessDecisionManager = new AffirmativeBased(accessDecisionVoters);         return accessDecisionManager;     }      @Override     @Autowired     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());     }      @Bean     public PasswordEncoder passwordEncoder(){         PasswordEncoder passwordEncoder = new PasswordEncoder();         passwordEncoder.setStringDigester(stringDigester());         return passwordEncoder;     }      @Bean     public PooledStringDigester stringDigester() {         PooledStringDigester psd = new PooledStringDigester();          psd.setPoolSize(2);         psd.setAlgorithm("SHA-256");         psd.setIterations(1000);         psd.setSaltSizeBytes(16);         psd.setSaltGenerator(randomSaltGenerator());          return psd;     }      @Bean     public RandomSaltGenerator randomSaltGenerator() {         RandomSaltGenerator randomSaltGenerator = new RandomSaltGenerator();         return randomSaltGenerator;     }      @Override     public void configure(WebSecurity web) throws Exception {         web.ignoring()                 .antMatchers("/static/**")                 .antMatchers("/i18n/**");     }      @Override     protected void configure(HttpSecurity http) throws Exception {         http             .csrf()         .and()             .formLogin()             .loginPage("/login")             .loginProcessingUrl("/checkLogin")             .defaultSuccessUrl("/home")             .failureUrl("/login?login_error=1")             .usernameParameter("username")             .passwordParameter("password")             .permitAll()         .and()             .logout()             .logoutUrl("/logout")             .logoutSuccessUrl("/login?isLoggedOut=1")             .deleteCookies("JSESSIONID")             .invalidateHttpSession(true)             .permitAll()         .and()             .authorizeRequests()             .antMatchers("/login**").permitAll()             .antMatchers("/error**").permitAll()             .antMatchers("/checkLogin**").permitAll()             .anyRequest()             .authenticated()             .accessDecisionManager(accessDecisionManager)         .and()             .exceptionHandling()             .accessDeniedPage("/accessDenied")         .and()             .headers()             .frameOptions()             .disable()         .and()             .sessionManagement()             .invalidSessionUrl("/login")             .maximumSessions(1);     }  } 

and my custom voter impl

@Component public class AccessDecisionVoterImpl implements AccessDecisionVoter {      @Autowired     private ModuleService moduleService;      @Override     public boolean supports(ConfigAttribute attribute) {         return true;     }      @Override     public boolean supports(Class clazz) {         return true;     }      @Override     public int vote(Authentication authentication, Object object, Collection collection) { // i have given this so that if user is not logged in then should not check permission at all          if(authentication instanceof AnonymousAuthenticationToken)             return ACCESS_DENIED;               HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();     String requestedOperation = request.getParameter("operation");      if (requestedOperation != null && !requestedOperation.isEmpty()){         String [] requestURISplit = request.getRequestURI().split("/");         String requestedModuleName = requestURISplit[2];          if(SecurityUtils.hasPermission(requestedModuleName, requestedOperation)){            return ACCESS_GRANTED;         }     } else {         return ACCESS_GRANTED;     }      return ACCESS_DENIED;  

Also, when I remove following lines from my voter, if the user is not logged in and tries to access the secured pages, it goes ahead. It should have redirected to the login page instead.

if(authentication instanceof AnonymousAuthenticationToken)                 return ACCESS_DENIED; 

This is the first time I am trying to use the spring boot. Hence, i am not sure of all the configuration issues.

Is there anything wrong with the order of the antMatchers?

Please help.

3 Answers

Answers 1

If I read this correctly, you are trying to solve the problem where your user is being logged in anonymously by returning ACCESS_DENIED for anonymous authentication. That's no good. Remove that custom code and instead do this in security config:

anyRequest().hasRole("USER") 

Then, ensure that when user is logged in, they have the required role.

An alternative would be to disable anonymous login so you won't need roles (for long term this would not be recommended).

http.anonymous().disable() 

Answers 2

The problem comes from your voter. Your voter should let anonymous access if the related "ConfigAttribute" is configured so. Below is what you should add (it may depends on your Spring Security version):

for (ConfigAttribute attribute : collection) {     if ("IS_AUTHENTICATED_ANONYMOUSLY".equals(attribute.getAttribute())) {         return ACCESS_GRANTED;     } } 

Answers 3

Your AccessDecisionVoter needs to either

  • abstain from voting (AccessDecisionVoter.ACCESS_ABSTAIN): if the voter is unable to make a decision (eg. user unauthorized, unable to obtain module from request context etc.)
  • grant access (AccessDecisionVoter.ACCESS_GRANTED): if the module can be identified and the user is authorized
  • deny access (AccessDecisionVoter.ACCESS_DENIED): if the module can be identified and the user is not authorized

With your AccessDecisionManager configuration, you basically cancel out the url-based access restrictions such as:

http.authorizeRequests()     .antMatchers("/css/**", "/img/**", "/js/**", "/font/**").permitAll()     .antMatchers("/login**").permitAll()     .antMatchers("/error**").permitAll()     .antMatchers("/checkLogin**").permitAll()     .anyRequest()         .authenticated() 

By default spring uses the WebExpressionVoter for that purpose. However, the AffirmativeBased AccessDecisionManager grants access if at least one AccessDecisionVoter grants access to a resource (this is probably not what you want). For your requirements a ConsensusBased AccessDecisionManager including the WebExpressionVoter would be the best match.

@Bean public AccessDecisionManager accessDecisionManager() {     List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();     decisionVoters.add(new WebExpressionVoter());     decisionVoters.add(new ModuleAccessDecisionVoter());     ConsensusBased consensusBased = new ConsensusBased(decisionVoters);      // deny access if voters equally voted to allow and deny access     consensusBased.setAllowIfEqualGrantedDeniedDecisions(false);     return consensusBased; } 

Your AccessDecisionVoter implementation:

static class ModuleAccessDecisionVoter implements AccessDecisionVoter<FilterInvocation> {      public int vote(Authentication authentication, FilterInvocation object, Collection<ConfigAttribute> attributes) {         if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {             return ACCESS_ABSTAIN;         }          // determine module and grant or deny access         // if module cannot be determined abstain from voting         String module = determineModule(object);         if (module != null) {             return isAccessGranted(module, authentication) ? ACCESS_GRANTED : ACCESS_DENIED         }          return ACCESS_ABSTAIN;     } } 

Anonymous access should lead to the following results:

  • /login: WebExpressionVoter: +1, ModuleVoter: 0 -> 1 = ACCESS_GRANTED
  • /foo-module: WebExpressionVoter: -1, ModuleVoter: -1 -> -2 = ACCESS_DENIED

Given a User that is allowed to view the Foo module should produce the following results:

  • /foo-module: WebExpressionVoter: +1, ModuleVoter: +1 -> 2 = ACCESS_GRANTED
  • /bar-module: WebExpressionVoter: +1 (because user is authenticated), ModuleVoter: -1 -> 0 = ACCESS_DENIED (because of ConsensusBased.setAllowIfEqualGrantedDeniedDecisions(false))
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment