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
- API works fine without Authorization
- It works fine if we Authorize only with the Authority (@PreAuthorize("hasAuthority('...')"))
- Scope is missing (empty list) in the Authientication.OAuth2Request when it reaches, OAuth2ExpressionUtils --> hasAnyScope().
- 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}
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 ?