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
@RestController
m 使用中的另一件事
这
@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]