Spring OAuth2 授权:访问被拒绝

Posted

技术标签:

【中文标题】Spring OAuth2 授权:访问被拒绝【英文标题】:Spring OAuth2 Authorization: Access Denied 【发布时间】:2020-04-26 15:12:15 【问题描述】:

我对 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> 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  | bcryptremoved | read       | client_credentials,password,refersh_token,authorization_code | http://localhost:8080/home | ROLE_PRODUCT_USER  |                 10800 |                2592000 | NULL                   | NULL        |
| writer    | product_api  | bcryptremoved | read,write | client_credentials,password,refersh_token,authorization_code | http://localhost:8080/home | ROLE_PRODUCT_ADMIN |                 10800 |                2592000 | NULL                   | NULL        |
+-----------+--------------+----------------------------------------------------------------------+------------+--------------------------------------------------------------+----------------------------+--------------------+-----------------------+------------------------+------------------------+-------------+

分析

    API 无需授权即可正常工作 如果我们只通过权威机构进行授权(@PreAuthorize("hasAuthority('...')")),它就可以正常工作 Authientication.OAuth2Request 到达时缺少范围(空列表), OAuth2ExpressionUtils --> hasAnyScope()。 范围由授权服务器的 /user 端点提供

authorities=[id=4, authority=ROLE_PRODUCT_USER], 详细信息=remoteAddress=127.0.0.1, sessionId=null, tokenValue=2f54e499-e47a-45fe-a6f6-e4c9593f9841,tokenType=Bearer, 解码细节=空,认证=真, userAuthentication=authorities=[id=4, authority=ROLE_PRODUCT_USER], 详细信息=clinet_id=读者,grant_type=密码, 用户名=product_user,认证=true,主体=密码=null, 用户名=product_user,权限=[id=4, authority=ROLE_PRODUCT_USER],accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, 凭据=null,名称=product_user,凭据=, oauth2Request=clientId=reader, scope=[read], requestParameters=clinet_id=reader, grant_type=password, 用户名=product_user,resourceIds=[product_api], 当局=[authority=ROLE_PRODUCT_USER],批准=真, refresh=false,redirectUri=null,responseTypes=[],extensions=, grantType=密码,refreshTokenRequest=null, 主体=密码=null,用户名=product_user,权限=[id=4, authority=ROLE_PRODUCT_USER],accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, clientOnly=false, name=product_user

    但在 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个参数是代表作用域的字符串Set,作为null传递!

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

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

【问题讨论】:

【参考方案1】:

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

我还想提请您注意Spring Security OAuth 项目已被弃用且不建议使用这一事实。

【讨论】:

这真是一个有价值的信息。非常感谢。我会避免使用自定义解决方案,而是坚持使用 spring 提供的开箱即用的功能。如果我想使用 Spring Boot 创建授权服务器,最好的选择是什么?当迁移文档 (github.com/spring-projects/spring-security/wiki/…) 说“由于 Spring Security 不提供授权服务器支持,迁移 Spring Security OAuth 授权服务器超出本文档的范围。”时,看起来我已经走到了尽头。 大量来自 spring 和其他来源的文档仍然在谈论 OAuth2 授权服务器!!。我浪费了将近一个月的时间试图理解它。当我开始使用 OAuth2 项目时,即使是最新版本的 Spring Tool Suite 也没有给出任何指示!。 @renjith 目前没有其他方法可以使用 Spring 构建授权服务器。我建议您考虑其他解决方案,例如 Keycloak。【参考方案2】:

@Renjith 请参考这个问题,我在那里回答了,如果你遇到任何其他挑战,请告诉我

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

@RestControllerm 使用中的另一件事 这 @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的更多详细信息

【讨论】:

以上是关于Spring OAuth2 授权:访问被拒绝的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:Oauth2:访问被拒绝(用户是匿名的);重定向到身份验证入口点

Spring Security OAuth2 Jwt 访问被拒绝

在 Spring OAuth2 中配置安全性:身份验证请求的访问被拒绝

任何用户的 jdbc-authentication 访问被拒绝 [OAUTH2]

Ldap 用户授权失败 - 未处理的 Spring 身份验证“访问被拒绝”

Spring安全登录错误页面,访问被拒绝