春季OAuth2授权:访问被拒绝

时间:2020-01-09 03:20:04

标签: java spring spring-boot spring-security oauth-2.0

我对Spring Security和OAuth2还是陌生的。作为学习的一部分,我试图设置 OAuth2授权服务器,并保护REST端点不受未经授权的访问。

我的资源服务器包含几个端点,并具有以下授权。

/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

问题:尝试使用以下方法访问端点时访问被拒绝 邮递员和grant_type =“ password”

代码

资源服务器

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配置

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

授权服务器

实现user-info-uri的主类

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;
        }

    }

数据库 oauth_client_details

mysql>从oauth_client_details中选择*,其中client_id位于('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        |
+-----------+--------------+----------------------------------------------------------------------+------------+--------------------------------------------------------------+----------------------------+--------------------+-----------------------+------------------------+------------------------+-------------+

分析

  1. API未经授权即可正常工作
  2. 如果我们仅通过授权进行授权(@PreAuthorize(“ hasAuthority('...')”))
  3. 当Authientication.OAuth2Request到达时,缺少范围(空列表), OAuth2ExpressionUtils-> hasAnyScope()。
  4. 范围是从授权服务器的/ user端点提供的

{authorities = [{id = 4,authority = ROLE_PRODUCT_USER}], details = {remoteAddress = 127.0.0.1,sessionId = null, tokenValue = 2f54e499-e47a-45fe-a6f6-e4c9593f9841,tokenType = Bearer, encodedDetails = null},authenticated = true, userAuthentication = {authorities = [{id = 4,authority = ROLE_PRODUCT_USER}], 详细信息= {clinet_id =阅读器,grant_type =密码, username = product_user},authenticated = true,principal = {password = null, 用户名= product_user,权限= [{id = 4, Authority = ROLE_PRODUCT_USER}],accountNonExpired = true, accountNonLocked = true,creditsNonExpired = true,enabled = true}, 凭据=空,名称= product_user},凭据=, oauth2Request = {clientId = reader, scope = [read] , requestParameters = {clinet_id =阅读器,grant_type =密码, username = product_user},resourceIds = [product_api], 权威= [{authority = ROLE_PRODUCT_USER}],已批准= true, refresh = false,redirectUri = null,responseTypes = [],extensions = {}, grantType =密码,refreshTokenRequest =空}, 委托人= {密码=空,用户名= product_user,权限= [{id = 4, Authority = ROLE_PRODUCT_USER}],accountNonExpired = true, accountNonLocked = true,creditsNonExpired = true,enabled = true}, clientOnly = false,name = product_user}

  1. 但是在UserInfoTokenServices.extractAuthentication()中创建OAuth2Request时并不能持久保存

    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);    }
    

第5个参数是一组表示范围的字符串,该字符串作为null传递!

OAuth2Request请求=新的OAuth2Request(空,this.clientId,空, 真,空, null,null,null,null);

我在这里缺少任何配置吗?

2 个答案:

答案 0 :(得分:2)

正如您已经注意到自己并在Issue #5096中所提到的那样,默认的UserInfoTokenServices不支持作用域,因此不支持#oauth2.hasScope功能。一种可能的解决方案是实现自定义ResourceServerTokenServices

我还想引起您注意Spring Security OAuth项目已过时,不建议使用的事实。

答案 1 :(得分:0)

@Renjith 请参考这个问题,我在那儿回答了,让我知道您是否还面临其他挑战

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

@RestController使用中的另一件事 这个 @PreAuthorize("hasRole('ROLE_PRODUCT_USER')")而非

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

由于GrantedAuthoritiesMapper返回的列表或GrantedAuthority的集合(授权服务器返回的身份验证的用户权限为ROLES),因此您需要使用@PreAuthorize("hasRole('ROLE_PRODUCT_USER')"),以便使用ROLES前缀

另请参阅 https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/oauth2login-advanced.html

有关自定义GrantedAuthoritiesMapper

的更多详细信息