1
.antMatchers("/secure2/**").authenticated();

I have configured one of my api as protected, when I try to access it, It gives me Access Denied error message, I do not know what could be the reason. Note that I am passing valid access token.

My scenario: Basically I have created logout rest api in authorization server and I want that, request with valid token is allowed to hit this api.

Request:

GET /auth/secure2 HTTP/1.1
Host: localhost:9191
Authorization: Bearer 33984141-1249-4465-a3aa-0b95a053fc63
Cache-Control: no-cache
Postman-Token: f4661790-a8e1-90ea-f6db-79cb37958cdf

Response:

{
    "timestamp": 1500186837033,
    "status": 403,
    "error": "Forbidden",
    "message": "Access Denied",
    "path": "/auth/secure2"
}

I found out that below method return false and due to that it raise the access denied error.

public final class ExpressionUtils {

    public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
        try {
            return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue();
        }
        catch (EvaluationException e) {
            throw new IllegalArgumentException("Failed to evaluate expression '"
                    + expr.getExpressionString() + "'", e);
        }
    }
}

Below are the screen shots which I captured by debugging in framework. Please also check the images mentioned in comment.

enter image description here

Code:

SecurityConfiguration.java :

import org.springframework.beans.factory.annotation.Autowired;


@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new StandardPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider
          = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(encoder());
        return authProvider;
    }

    @Bean
    public ShaPasswordEncoder encoder() {
        return new ShaPasswordEncoder(256);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
         http
         .exceptionHandling()
         .accessDeniedHandler(accessDeniedHandler())
         .and()
         .csrf()
         .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
         .disable()
         .headers()
         .frameOptions().disable().disable()
         .sessionManagement()
         .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
         .and()
         .authorizeRequests()
         .antMatchers("/hello/").permitAll()
         .antMatchers("/secure3/").permitAll()
         .antMatchers("/oauth/token/revoke/**").authenticated()
         .antMatchers("/secure2/**").authenticated();

    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return new CustomAccessDeniedHandler();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
    private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {

        public GlobalSecurityConfiguration() {
        }
        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() {
            return new OAuth2MethodSecurityExpressionHandler();
        }

    }

}

Oauth2Configuration.java

@Configuration
public class OAuth2Configuration {


    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

        private static final String ENV_OAUTH = "authentication.oauth.";
        //private static final String PROP_CLIENTID = "clientid";
        //private static final String PROP_SECRET = "secret";
        private static final String PROP_ACCESS_TOKEN_VALIDITY_SECONDS = "accessTokenValidityInSeconds";
        private static final String PROP_REFRESH_TOKEN_VALIDITY_SECONDS = "refreshTokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Autowired
        private DataSource dataSource;

        @Autowired
        private CustomUserDetailService userDetailsService;

        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints.tokenStore(tokenStore())
                     .userDetailsService(userDetailsService)
                     .tokenEnhancer(tokenEnhancer())
                     .accessTokenConverter(accessTokenConverter())
                     .authenticationManager(authenticationManager);
        }

        @Bean
        public TokenEnhancer tokenEnhancer() {
           return new CustomTokenEnhancer();
        }

        @Bean
        public DefaultAccessTokenConverter accessTokenConverter() {
            return new DefaultAccessTokenConverter();
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer)
                throws Exception {
            oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
        }


        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients
                    .inMemory()

                    .withClient("clientId")
                    .scopes("read", "write")
                    .authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
                    .authorizedGrantTypes("password", "refresh_token")
                    .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_ACCESS_TOKEN_VALIDITY_SECONDS, Integer.class, 80))
                    .refreshTokenValiditySeconds(propertyResolver.getProperty(PROP_REFRESH_TOKEN_VALIDITY_SECONDS, Integer.class, 180))

                    .and().inMemory()
                    .withClient("clientid")
                    .scopes("read", "write")
                    .authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
                    .authorizedGrantTypes("client_credentials")
                    .secret("secret");

        }

        @Override
        public void setEnvironment(Environment environment) {
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        }

    }

Controller:

@Controller
@RequestMapping("/secure2")
public class SecureController1 {

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String sayHello() {

        return "Secure Hello secure2!";
    }

}

What are the scenarios in which it raises access denied error message? Please let me know if any other information in needed.

Nitin Bisht
  • 5,053
  • 4
  • 14
  • 26
kpatel
  • 29
  • 1
  • 4
  • https://i.stack.imgur.com/vmOff.png – kpatel Jul 16 '17 at 06:48
  • https://i.stack.imgur.com/CwslH.png – kpatel Jul 16 '17 at 06:48
  • This is authorization server, and I want to protect some resources in authorization server. say for example I have token revocation/logout rest api that I want to protect. Request with valid access token is allowed for logout. – kpatel Jul 16 '17 at 11:18
  • I have related configuration inside SecurityConfiguration.configure(httpsecurity) method. – kpatel Jul 16 '17 at 11:24
  • 1
    What resources are in the authorization server? There should be only the authorize and token endpoints. All other endpoints are part of the resource server. However, if you call the authorization server the OAth2 token is not applied and you are anonymous. Only the resource server checks the token.That's the reason for your 403. – dur Jul 16 '17 at 11:47
  • If you want to use the OAuth2 token in authorization server, you have to check it yourself, see for logout example [here](https://stackoverflow.com/a/32320860/5277820). – dur Jul 16 '17 at 11:53
  • so do you mean that there is no default support(configuration) with which we can permit/restrict resources in authorization server? and we can not keep protected rest api inside auth server other then token endpoints without manual checking? Basically I want my logout endpoint which resides in auth server to be called with valid token only. – kpatel Jul 16 '17 at 12:01
  • AFAIK the concept of OAuth2 is that there is a clear separation of authorization server for authorization and a resource server for resources. There is no default logout endpoint on authorization server. I guess the problem is, that a logout on authorization server is not working, because resource server doesn't know of the logout in any case. If the resource server caches the token, it will accept the token still after logout on authorization server. But I'm not sure. – dur Jul 16 '17 at 12:46

1 Answers1

1

I used these codes and they worked well.

OAuth2AuthorizationServerConfig.java:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    // Configure the token store and authentication manager
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //@formatter:off
        endpoints
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter()) // added for JWT
                .authenticationManager(authenticationManager);
        //@formatter:on
    }

    // Configure a client store. In-memory for simplicity, but consider other
    // options for real apps.


    //It is not necessary.works even without this func:)
//    @Override
//    public void configure(AuthorizationServerSecurityConfigurer oauthServer)
//            throws Exception {
//        oauthServer
//                .tokenKeyAccess("permitAll()")
//                .checkTokenAccess("isAuthenticated()");
//    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //@formatter:off
        clients
                .inMemory()
                .withClient("myclient")//username in basic auth header
                .secret ("{noop}123")//password in basic auth header;
                .authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials", "refresh_token")
                .scopes("read")
                //.redirectUris("http://localhost:9191/x")
                .accessTokenValiditySeconds(86400); // 24 hours
        //@formatter:on
    }

    // A token store bean. JWT token store
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter()); // For JWT. Use in-memory, jdbc, or other if not JWT
    }

    // Token converter. Needed for JWT
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123"); // symmetric key
        return converter;
    }

    // Token services. Needed for JWT
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }


//    @Bean
//
//    public PasswordEncoder passwordEncoder () {
//
//        return new BCryptPasswordEncoder();
//
//    }
}

WebSecurityConfig:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;


    //It is not important to use this func. /oauth/token is the default path of spring security to use oauth2. she is so clever!! :)
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//
//
//        http.authorizeRequests()
//                .antMatchers(HttpMethod.POST, "/oauth/token").permitAll()
//                .anyRequest().authenticated();
//    }

    @Autowired
    public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("{noop}user").roles("ROLE");
    }

//
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.userDetailsService(userDetailsService)
//                .passwordEncoder(passwordEncoder());
//    }


    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

//
//    @Bean
//    public PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
//    }
}

Output: Please notice that the values of username and password used in code and postman, must be the same.

enter image description here enter image description here

Hope to be helpful:)

ropa8
  • 339
  • 1
  • 6