2
votes

I am pretty new to spring security & OAuth2. As a part of the learning I am trying to set up an OAuth2 Authorization server and protect a REST end point from unauthorized access.

My resource server contains couple of end points, with the following Authorization.

/products : only user with Authority='ROLE_PRODUCT_USER' and scope='read' can access this endpoint
/addProduct :  only user with Authority='ROLE_PRODUCT_ADMIN' and scope='write' can access this endpoint

ISSUE: Access denied while trying to access the end points using postman and grant_type="password"

CODE

Resource Server

ProductController.java

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @PreAuthorize("#oauth2.hasScope('read') and hasAuthority('ROLE_PRODUCT_USER')")
    @GetMapping("/products")
    public ResponseEntity<List<Product>> getAllProducts() {
        return new ResponseEntity<List<Product>>(productService.getAllProducts(), HttpStatus.OK);
    }

    @PreAuthorize("#oauth2.hasScope('write') and hasAuthority('ROLE_PRODUCT_ADMIN')")
    @PostMapping("/addproduct")
    public ResponseEntity<Product> addProduct(@RequestBody Product product) {
        return new ResponseEntity<Product>(productService.addProduct(product), HttpStatus.OK);
    }



}

OAuth config in Resource Server

security:
  oauth2:
    resource:
      user-info-uri: http://localhost:9090/user

Authorization Server

Main Class where the user-info-uri is implemented

import java.security.Principal;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

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

        @GetMapping("/user")
        public Principal user(Principal user) {
            System.out.println(user);
            return user;
        }

    }

Database oauth_client_details

mysql> select * from oauth_client_details where client_id in ('reader','writer');

+-----------+--------------+----------------------------------------------------------------------+------------+--------------------------------------------------------------+----------------------------+--------------------+-----------------------+------------------------+------------------------+-------------+
| client_id | resource_ids | client_secret                                                        | scope      | authorized_grant_types                                       | web_server_redirect_uri    | authorities        | access_token_validity | refresh_token_validity | additional_information | autoapprove |
+-----------+--------------+----------------------------------------------------------------------+------------+--------------------------------------------------------------+----------------------------+--------------------+-----------------------+------------------------+------------------------+-------------+
| reader    | product_api  | {bcrypt}removed | read       | client_credentials,password,refersh_token,authorization_code | http://localhost:8080/home | ROLE_PRODUCT_USER  |                 10800 |                2592000 | NULL                   | NULL        |
| writer    | product_api  | {bcrypt}removed | read,write | client_credentials,password,refersh_token,authorization_code | http://localhost:8080/home | ROLE_PRODUCT_ADMIN |                 10800 |                2592000 | NULL                   | NULL        |
+-----------+--------------+----------------------------------------------------------------------+------------+--------------------------------------------------------------+----------------------------+--------------------+-----------------------+------------------------+------------------------+-------------+

ANALYSIS

  1. API works fine without Authorization
  2. It works fine if we Authorize only with the Authority (@PreAuthorize("hasAuthority('...')"))
  3. Scope is missing (empty list) in the Authientication.OAuth2Request when it reaches, OAuth2ExpressionUtils --> hasAnyScope().
  4. Scope is supplied from the /user endpoint of Authorization server

{authorities=[{id=4, authority=ROLE_PRODUCT_USER}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=2f54e499-e47a-45fe-a6f6-e4c9593f9841, tokenType=Bearer, decodedDetails=null}, authenticated=true, userAuthentication={authorities=[{id=4, authority=ROLE_PRODUCT_USER}], details={clinet_id=reader, grant_type=password, username=product_user}, authenticated=true, principal={password=null, username=product_user, authorities=[{id=4, authority=ROLE_PRODUCT_USER}], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true}, credentials=null, name=product_user}, credentials=, oauth2Request={clientId=reader, scope=[read], requestParameters={clinet_id=reader, grant_type=password, username=product_user}, resourceIds=[product_api], authorities=[{authority=ROLE_PRODUCT_USER}], approved=true, refresh=false, redirectUri=null, responseTypes=[], extensions={}, grantType=password, refreshTokenRequest=null}, principal={password=null, username=product_user, authorities=[{id=4, authority=ROLE_PRODUCT_USER}], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true}, clientOnly=false, name=product_user}

  1. But it is not persisted while creating an OAuth2Request in UserInfoTokenServices.extractAuthentication()

    private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
    Object principal = getPrincipal(map);
    List<GrantedAuthority> authorities = this.authoritiesExtractor
            .extractAuthorities(map);
    OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
            null, null, null, null);
    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
            principal, "N/A", authorities);
    token.setDetails(map);
    return new OAuth2Authentication(request, token);    }
    

Here the 5th paramter is a Set of string that represents scopes, which is passed as null!

OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null, null, null, null, null);

Am i missing any configuration here ?

2

2 Answers

2
votes

As you already noticed yourself and as mentioned in the Issue #5096, the default UserInfoTokenServices does not support scopes, hence #oauth2.hasScope functionality. A possible solution would be to implement a custom ResourceServerTokenServices.

I also want to draw your attention to the fact that the Spring Security OAuth project is deprecated and is not recommended for use.

0
votes

@Renjith Please refer to this question, I answered it there, let me know if u face any other challenges

How to set user authorities from user claims return by an oauth server in spring security

Another thing in your @RestControllerm use this @PreAuthorize("hasRole('ROLE_PRODUCT_USER')") and not

@PreAuthorize("#oauth2.hasScope('read') and hasAuthority('ROLE_PRODUCT_USER')")

Since the GrantedAuthoritiesMapper is returning a list or set of GrantedAuthority, which your authorization server is returning the authenticated user authorities as ROLES, then you need to use the @PreAuthorize("hasRole('ROLE_PRODUCT_USER')") so it can use the ROLES prepend

Also refer to https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/oauth2login-advanced.html

for more details on customizing the GrantedAuthoritiesMapper