user672009
user672009

Reputation: 4585

hasRole always return 403

I can't seem to get my security configuration right. No matter what I do when using hasRole my endpoints always return 403.

Also I can't get anything to work unless I duplicate my antMatchers under both .requestMatchers() and .authorizeRequests(). I'm clearly missing something here.

Basically I want everything to require authentication but a few endpoints only to be accessable if the user is member of certain groups (for now just admin).

My security configuration is as follows. Everything beside hasRole works.

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .requestMatchers()
                .antMatchers(HttpMethod.GET, "/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html")
                .antMatchers(HttpMethod.GET, "/users")
                .and()
            .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html").permitAll()
                .antMatchers(HttpMethod.GET, "/users").hasRole("ADMIN")    
                .anyRequest().authenticated();
    }

    // Inspiration: https://spring.io/blog/2015/06/08/cors-support-in-spring-framework#comment-2416096114
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
                .antMatchers(HttpMethod.OPTIONS, "/**");
    }
}

My AuthenticationConfiguration is as follows

@Configuration
@EnableResourceServer
public class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
    private final UserDetailsService userService;
    private final PasswordEncoder passwordEncoder;

    public AuthenticationConfiguration(UserDetailsService userService, PasswordEncoder passwordEncoder) {
        this.userService = userService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userService)
                .passwordEncoder(passwordEncoder);
    }
}

My AuthorizationServerConfiguration is as follows

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    private final AuthenticationManager authenticationManager;

    public AuthorizationServerConfiguration(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("html5")
                .secret("password")
                .authorizedGrantTypes("password")
                .scopes("openid");
    }
}

I'll happily post my user service and other stuff. But everything seems to work beside hasRole and Principal is loaded with the right authorities (roles). But please let me know if I should post any more code.

The entire source code can be found here.

Upvotes: 6

Views: 2946

Answers (3)

Phellipe Sander
Phellipe Sander

Reputation: 16

I had the same problem, I just forgot to implements getAuthorities() method from UserDetails (SpringSecurity class). Look my entity:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Entity
@Table(name = "tb_user")
public class User implements UserDetails, Serializable {

private static final long serialVersionUID = -6519124777839966091L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;

@Column(unique = true)
private String email;
private String password;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
        name = "tb_user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();

public User() {
}

public User(Long id, String firstName, String lastName, String email, String password) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    this.password = password;
}

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public void setPassword(String password) {
    this.password = password;
}

public Set<Role> getRoles() {
    return roles;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return roles.stream().map(role -> new SimpleGrantedAuthority(role.getAuthority()))
            .collect(Collectors.toList());
}

public String getPassword() {
    return password;
}

@Override
public String getUsername() {
    return email;
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    return true;
}


@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return Objects.equals(id, user.id);
}

@Override
public int hashCode() {
    return Objects.hash(id);
}
}

The method getAuthorities return null by default when you extends UserDetails class from security package, you need implement something like that:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return roles.stream().map(role -> new SimpleGrantedAuthority(role.getAuthority()))
        .collect(Collectors.toList());
}

I hope this help someone, sorry about my english errors! hehe

Upvotes: 0

Riiverside
Riiverside

Reputation: 798

Following up on my comments to the question I'll provide sample OAuth2 Configuration classes I've used for testing. I always use two different webapps, because I want a clear line between auth server and resource server(and because it makes configurations so much harder....), so my example probably needs some adjustments when used in a single webapp.

Configuration for the auth server:

@EnableAuthorizationServer
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    private TokenStore tokenStore;
    private DataSource dataSource;

    @Autowired
    public OAuth2Config(TokenStore tokenStore,
                        DataSource dataSource) {
        this.tokenStore = tokenStore;
        this.dataSource = dataSource;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

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


    @Configuration
    public static class TokenStoreConfiguration {
        @Bean
        public TokenStore tokenStore(DataSource dataSource) {
            return new JdbcTokenStore(dataSource);
        }
    }
}

Configuration for resource server:

@EnableResourceServer
@Configuration
public class OAuth2Config extends ResourceServerConfigurerAdapter {
    public static final String PROPERTY_RESOURCE_ID = "com.test.oauth.resourceId";

    private Environment environment;
    private TokenStore tokenStore;

    @Autowired
    public OAuth2Config(Environment environment,
                        TokenStore tokenStore) {
        this.environment = environment;
        this.tokenStore = tokenStore;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore)
                .resourceId(environment.getProperty(PROPERTY_RESOURCE_ID))
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/demo")
                    .access("hasRole('DEMO')")

                .anyRequest().denyAll()
                .and()
                .formLogin().disable()
                .logout().disable()
                .jee().disable()
                .x509().disable();
    }

    @Configuration
    public static class TokenStoreConfiguration {
        @Bean
        public TokenStore tokenStore(DataSource dataSource) {
            return new JdbcTokenStore(dataSource);
        }
    }
}

Obviously this requires that you have a DataSource bean configured. This implementation uses the default tables as provided by spring security OAuth2(they are far from ideal, but can be customized if required).

There are a few things you might want to adjust for your case(I'll leave the classes I provided as is for a reference if people might want to use it with JDBC):

  1. Create only one bean of type TokenStore and use InMemoryTokenStore instead of JdbcTokenStore
  2. replace the configuration for clients with your inMemory() implementation and remove all references to my autowired DataSource
  3. Provide requestMatchers() before specifying authorizeRequests() in your resource server configuration. Depending on the order the configuration is processed and the filter chains are added this might be required to allow the oauth endpoints to be reached without requiring an OAuth token.

Edit: Seeing the answer by ritesh.garg I think that what I provided might not resolve your issues, but might help some figuring out where and how to start configuring Spring Security OAuth2(When I did it the first time I found it hard to do, because back then I couldn't find any clear examples, though this might have changed)

Upvotes: 0

ritesh.garg
ritesh.garg

Reputation: 3943

Have you tried with "ROLE_ADMIN" rather than just "ADMIN"? Take a look at this for reference:

Spring security added prefix "ROLE_" to all roles name?

Upvotes: 2

Related Questions