Spring oauth2 hasRole access denied

2019-06-26 05:05发布

问题:

I am really new to OAuth2 and trying to build one server in roles auth.server for authorizing users and one keeping a protected resource...

I've got issues to secure with the ResourceServerConfigurerAdapter. It seems like he is ignoring all it's roles fetching form userInfoUrl...

so here the code:

AuthServer

@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class Oa2AuthServerApplication {

    @RequestMapping("/user")
    public Principal user(Principal user) {
        return user;
    }
    public static void main(String[] args) {
        SpringApplication.run(Oa2AuthServerApplication.class, args);
    }
}

__

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("admin")
                .roles("ADMIN", "USER")
                .and()
                .withUser("user")
                .password("user")
                .roles("USER");
    }
}

__

@Configuration
public class OA2AuthConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("default")
                .secret("kx")
                .scopes("AUTH", "TRUST")
                .autoApprove(true)
                .authorities("ROLE_GUEST", "ROLE_USER", "ROLE_ADMIN")
                .authorizedGrantTypes("authorization_code", "implicit", "refresh_token");
    }
}

ResourceServer

@SpringBootApplication
@RestController
@EnableResourceServer
public class Oa2ResourceServerApplication {
    @RequestMapping("/")
    public String greet() {
        return UUID.randomUUID().toString() + "\r\n";
    }

    @RequestMapping("/forAdmin")
    public String admin() {
        return "hi admin!";
    }


    public static void main(String[] args) {
        SpringApplication.run(Oa2ResourceServerApplication.class, args);
    }
}

So getting token from authserver + calling "localhost:9091/" and "/forAdmin" works with this token.

But when I do this:

public class WebSecurityConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/forAdmin").hasRole("USER");
    }

I get access denied....

to be sure, the roles are reaching the resource server, i have changed the geet() from above to

@RequestMapping("/")
    public String greet(Principal user) {
        if (user instanceof OAuth2Authentication) {
            log.info("having roles: {}", ((OAuth2Authentication) user).getAuthorities());
        }
        return UUID.randomUUID().toString() + "\r\n";
    }

and the console shows

d.k.auth.Oa2ResourceServerApplication : having roles: [{authority=ROLE_USER}]

So when "Principal" is the currently authenticated user, I assume there is a bug with the resourceserverer configurer....or I am doing something fatally wrong...

or both....I don't know

does anybody can help me in this problem?

回答1:

So JWT is necessary, without it does not work.

I solved it with the combination:

@PreAuthorize("#oauth2.hasScope('openid') and hasRole('ROLE_ADMIN')")

You can find a sample of a protected Resource here.



回答2:

I had a simililar problem when connecting a client to my AuthServer. I found out that when the client is parsing the roles given from the server it uses a AuthoritiesExtractor. Default one used is FixedAuthoritiesExtractor.

The code in the FixedAuthoritiesExtractor has a method to convert a Map with authorities to a List of GrantedAuthority and as part of this it fetches the name of the roles in a method called asAuthorities.

private String asAuthorities(Object object) {
    if (object instanceof Collection) {
        return StringUtils.collectionToCommaDelimitedString((Collection<?>) object);
    }
    if (ObjectUtils.isArray(object)) {
        return StringUtils.arrayToCommaDelimitedString((Object[]) object);
    }
    return object.toString();
}

When debugging this i can see that the Object comming in is a List but the content of that list is a Map. So its a List<Map<String,String>>. Inside the map it contains a entry with key authority and value role.

Lets say we have the role ROLE_USER on the AuthServer. When using toString on a map object it converts this to the String {authority=ROLE_USER}. If you check if the user now contains a role name ROLE_USER it will not be equal to the name {authority=ROLE_USER}

I therefore created a new version of the AuthoritiesExtractor.

public class OAuth2AuthoritiesExtractor implements AuthoritiesExtractor {

    static final String AUTHORITIES = "authorities";

    @Override
    public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
        String authorities = "ROLE_USER";
        if (map.containsKey(AUTHORITIES)) {
            authorities = asAuthorities(map.get(AUTHORITIES));
        }
        return AuthorityUtils.commaSeparatedStringToAuthorityList(authorities);
    }

    @SuppressWarnings("unchecked")
    private String asAuthorities(Object object) {
        if (object instanceof Collection) {
            return (String) ((Collection) object).stream().map(o -> {
                if (o instanceof Map) {
                    return ((Map) o).values().stream().collect(Collectors.joining(","));
                }
                return o.toString();
            }).collect(Collectors.joining(","));

        }
        if (ObjectUtils.isArray(object)) {
            return StringUtils.arrayToCommaDelimitedString((Object[]) object);
        }
        return object.toString();
    }

}

When using this extractor it detects that the collection contains a Map - and if it does it uses the values in that map as role names.

The role i get now in spring security is stripped of the {authority= part and now only contains the String ROLE_USER, and the checks to see if isUserInRole now works.



回答3:

I think you are missing roles prefixing.

From Spring Security 4.x roles has to be prefixed, for e.g. if you are doing

.antMatchers("/forAdmin").hasRole("USER");

you have to change it to:

.antMatchers("/forAdmin").hasRole("ROLE_USER");

Roles are processed by the RoleVoter and having a prefix lets the voter know which tokens are role names so it can ignore ones it can't process. For example you can specify "ROLE_ADMIN,IS_AUTHENTICATED_FULLY" but you wouldn't want that voter to process IS_AUTHENTICATED_FULLY - AuthenticatedVoter should handle that.

From the official documentation:

Votes if any ConfigAttribute.getAttribute() starts with a prefix indicating that it is a role. The default prefix string is ROLE_, but this may be overridden to any value. It may also be set to empty, which means that essentially any attribute will be voted on. As described further below, the effect of an empty prefix may not be quite desirable.



回答4:

The problem is, that token exchange via userInfoUri is not working properly. You are able to secure resources servers from beeing accessed unauthorized, but the access() method in HttpSecurity configuration always seem to deny a request.

Adding a JWT token store fixed this.

I wrapped a more detailed explanation of this into a blog article here: stytex.de/blog/2016/02/01/spring-cloud-security-with-oauth2/