Spring Security 中具有密码授予的 oAuth2 客户端

2024-04-10

我正在使用一组受 oAuth2 保护的服务。目前的工作原理如下:客户端使用用户名和密码登录。我用这些换取代币。我将令牌保留在会话中,并在每次想要调用服务时提交它。它可以工作,但问题是我完全手动执行此操作,而没有使用 Spring Security oAuth2 支持的大部分内容。 它看起来是这样的:

<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
    <authentication-provider ref="oAuth2AuthenticationProvider"/>
</authentication-manager>


<beans:bean id="oAuth2AuthenticationProvider" class="my.custom.Oauth2AuthenticationProvider">
    <beans:constructor-arg name="accessTokenUri" value="http://x.x.x.x/oauth/token"/>
    <beans:constructor-arg name="clientId" value="myClientId"/>
    <beans:constructor-arg name="clientSecret" value="myClientSecret"/>
    <beans:constructor-arg name="scope">
        <beans:list>
            <beans:value>myScope</beans:value>
        </beans:list>
    </beans:constructor-arg>
</beans:bean>

<beans:bean id="resourceOwnerPasswordAccessTokenProvider" class="org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider"/>

如您所见,我自己创建了身份验证提供程序。它正在接受标准UsernamePasswordAuthenticationToken但正在制作我自己的扩展,以保留实际的OAuth2AccessToken以及,从而将其保留在安全上下文中。

public class Oauth2AuthenticationProvider implements AuthenticationProvider {

@Autowired
private ResourceOwnerPasswordAccessTokenProvider provider;

private String accessTokenUri;
private String clientId;
private String clientSecret;
private List<String> scope;

public Oauth2AuthenticationProvider(String accessTokenUri, String clientId, String clientSecret, List<String> scope) {
    this.accessTokenUri = accessTokenUri;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.scope = scope;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String password = authentication.getCredentials().toString();
    OAuth2AccessToken token = obtainToken(username, password);
    return handleLogonSuccess(authentication, token);
}

private OAuth2AccessToken obtainToken(String username, String password) {
    ResourceOwnerPasswordResourceDetails passwordResourceDetails = new ResourceOwnerPasswordResourceDetails();
    passwordResourceDetails.setUsername(username);
    passwordResourceDetails.setPassword(password);
    passwordResourceDetails.setClientId(clientId);
    passwordResourceDetails.setClientSecret(clientSecret);
    passwordResourceDetails.setScope(scope);
    passwordResourceDetails.setAccessTokenUri(accessTokenUri);
    DefaultAccessTokenRequest defaultAccessTokenRequest = new DefaultAccessTokenRequest();
    OAuth2AccessToken token;
    try {
        token = provider.obtainAccessToken(passwordResourceDetails, defaultAccessTokenRequest);
    } catch (OAuth2AccessDeniedException accessDeniedException) {
        throw new BadCredentialsException("Invalid credentials", accessDeniedException);
    }

    return token;
}

public OAuth2AccessToken refreshToken(OAuth2AuthenticationToken authentication) {
    OAuth2AccessToken token = authentication.getoAuth2AccessToken();
    OAuth2RefreshToken refreshToken = token.getRefreshToken();
    BaseOAuth2ProtectedResourceDetails resourceDetails = new BaseOAuth2ProtectedResourceDetails();
    resourceDetails.setClientId(clientId);
    resourceDetails.setClientSecret(clientSecret);
    resourceDetails.setScope(scope);
    resourceDetails.setAccessTokenUri(accessTokenUri);
    OAuth2AccessToken newToken = provider.refreshAccessToken(resourceDetails, refreshToken, new DefaultAccessTokenRequest());
    authentication.setoAuth2AccessToken(newToken);
    return newToken;
}

public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

private Authentication handleLogonSuccess(Authentication authentication, OAuth2AccessToken token) {

    MyCustomOAuth2AuthenticationToken successAuthenticationToken = new MyCustomOAuth2AuthenticationToken(user, authentication.getCredentials(), calculateAuthorities(authentication), token);

    return successAuthenticationToken;
}

public list<GrantedAuthority> calculateAuthorities(Authentication authentication) {
        //my custom logic that assigns the correct role. e.g. ROLE_USER
}

}

如您所见,它基本上确保令牌保留在安全范围内,我可以在每次调用后端服务之前手动提取它。同样,我会在每次调用之前检查令牌的新鲜度。 这很有效,但我确信我可以在 XML 中使用 Spring 的 oauth 命名空间(我没有使用 Java 配置)以更多配置、更少代码的方式实现相同的目的。我发现的大多数示例都包括 oAuth 服务器实现,我不关心它,只是让我感到困惑。

谁能帮我解决这个问题吗?


我通过浏览 Spring Security OAuth 源代码和在线找到的其他解决方案的零散部分,整合了一个类似的解决方案。我正在使用 Java Config,但也许它可以帮助您映射到 xml 配置,如下所示:

@Configuration
@EnableOAuth2Client
public class RestClientConfig {

    @Value("${http.client.maxPoolSize}")
    private Integer maxPoolSize;

    @Value("${oauth2.resourceId}")
    private String resourceId;

    @Value("${oauth2.clientId}")
    private String clientId;

    @Value("${oauth2.clientSecret}")
    private String clientSecret;

    @Value("${oauth2.accessTokenUri}")
    private String accessTokenUri;


    @Autowired
    private OAuth2ClientContext oauth2ClientContext;


    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    @Bean
    public HttpClient httpClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(maxPoolSize);
        // This client is for internal connections so only one route is expected
        connectionManager.setDefaultMaxPerRoute(maxPoolSize);
        return HttpClientBuilder.create().setConnectionManager(connectionManager).build();
    } 

    @Bean
    public OAuth2ProtectedResourceDetails oauth2ProtectedResourceDetails() {
        ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
        details.setId(resourceId);
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        return details;
    }

    @Bean
    public AccessTokenProvider accessTokenProvider() {
        ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
        tokenProvider.setRequestFactory(httpRequestFactory());
        return new AccessTokenProviderChain(
                  Arrays.<AccessTokenProvider> asList(tokenProvider)
                );
    }

    @Bean
    public OAuth2RestTemplate restTemplate() {
        OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2ProtectedResourceDetails(), oauth2ClientContext);
        template.setRequestFactory(httpRequestFactory());
        template.setAccessTokenProvider(accessTokenProvider());
        return template;
    }   
}

我发现的一个重要的一点是,即使对于单个提供程序,您也需要使用 AccessTokenProviderChain,否则自动令牌刷新(身份验证后)将无法工作。

要在第一个请求上设置用户凭据,您需要:

@Autowired
private OAuth2RestTemplate restTemplate;

restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", username);
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", password);

然后您可以使用 RestTemplate 方法正常发出请求,例如:

    String url = "http://localhost:{port}/api/users/search/findByUsername?username={username}";

    ResponseEntity<User> responseEntity = restTemplate.getForEntity(
            url, User.class, 8081, username);

如果你想跟踪网络上的请求,你可以将 apache http 客户端上的日志级别设置为 DEBUG,例如使用 Spring 引导:

logging.level.org.apache.http=调试

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Security 中具有密码授予的 oAuth2 客户端 的相关文章

随机推荐

  • 使用 fnmatch.filter 按多个可能的文件扩展名过滤文件

    给出以下一段 python 代码 for root dirs files in os walk directory for filename in fnmatch filter files png pass 如何过滤多个扩展名 在这种特殊情
  • 向 python 服务器添加超时时出现非阻塞错误

    我正在用 python 编写一个简单的 TCP 服务器 并尝试输入超时 我当前的代码 import socket def connect HOST Symbolic name meaning the local host PORT 5007
  • 我可以将 artifactId 转换为我的 Maven 原型中的类名前缀吗?

    我正在创建一个 Maven 原型 并在生成的项目中想要一个以生成的项目的工件 id 命名的类 工件 ID 的格式如下 the project name并且该类应命名为TheProjectNameMain 我尝试在我的archetype me
  • 将base64图像上传到亚马逊s3

    我在尝试将图像上传到 AWS S3 时遇到一些问题 似乎可以正确上传文件 但是每当我尝试下载或预览时 它都无法打开 目前 这是我正在使用的上传代码
  • Flutter Socket.listen()接收不完整数据

    使用套接字接收字符串数据 而且好像传输不完整 服务器发送大约 80 KB flutter 套接字有时接收 1 KB 有时接收 10 KB 左右 尝试了 onDone 处理程序 它是相同的 在接收到整个数据之前调用它 我还尝试将数据分成多个部
  • 将控制台输出重定向到单独程序中的文本框

    我正在开发一个 Windows 窗体应用程序 它需要我调用一个单独的程序来执行任务 该程序是一个控制台应用程序 我需要将标准输出从控制台重定向到程序中的文本框 我从我的应用程序执行程序没有问题 但我不知道如何将输出重定向到我的应用程序 我需
  • 如何查询 firestore() 的 graphQL 解析器?

    我将 GraphQL 应用程序与现有的 Firebase 项目结合起来 在获取查询以正确从 firestore 获取数据时遇到很多问题 到目前为止 我的突变工作正常 但是当我去查询数据时 我无法将 firestore get 快照转换为 g
  • html5 video safari 在播放前下载完整

    我想知道为什么我的 mp4 html5 视频不是 流式传输 而是等到完全下载后才开始在 safari 中播放 www pija se 我已经尝试过 QTIndexSwapper 但它说索引位于正确的位置 任何帮助表示赞赏 看起来 MOOV
  • heroku 上的 Gunicorn:绑定到本地主机

    我一直在关注教程https devcenter heroku com articles django declare process types with procfile https devcenter heroku com articl
  • 替换字符串中的特定单词 (Python)

    我想替换字符串句子中的单词 例如 What noun is verb 用实际名词 动词替换 包括 中的字符的正则表达式是什么 您不需要为此使用正则表达式 我会做 string What noun is verb print string r
  • Google Cloud SQL 连接到 flutter

    我在互联网上呆了 3 天 试图找到一些可以帮助我在 Google Cloud 和我的 flutter 应用程序上设置 PostreSQL 服务器的东西 无论是文档还是互联网上的任何地方都没有关于 flutter 应用程序如何连接 设置甚至在
  • AlarmManager 在模拟器中运行良好,但在真实设备中运行不佳

    这是我设置闹钟的代码 public void SetAlarm Context context int tag long time AlarmManager am AlarmManager context getSystemService
  • 将每 2 个 div 包裹在一个新的 div 中

    假设我有这个 div class somediv div div class somediv div div class somediv div div class somediv div div class somediv div div
  • Qt Designer 不加载我的自定义小部件插件

    我正在阅读 使用 Qt4 进行 C GUI 编程 一书 并且已经达到了将自定义小部件与 Qt Designer 集成的主题 我已经构建了那里概述的示例项目 图标编辑器插件 并且我得到了一个名为 libiconeditorplugin so
  • 如何强制执行新的空 EF 迁移?

    好的 所以我完全依赖我的迁移和种子代码来维护所有数据库结构和初始数据 因此 我面临的情况是 我在此版本中所做的所有更改都是直接在数据库 存储过程和更新 上进行的 并且 C 代码本身没有任何更改 问题是 由于我想使用新的迁移来执行这些数据库特
  • 生产中的 GWT 源映射

    GWT 支持超级开发模式下的源映射 不幸的是 尽管我在 gwt xml 文件中添加了源映射选项 但它们似乎无法在生产模式下工作 如何在那里启用它们 看看 GWT 自己的网站是如何做到这一点的 https gwt googlesource c
  • 如何离线安装Flask? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我已经在我的电脑上下载了 Flask 然后我就断开了连接 现在我需要在没有互联网连接的情况下安装 Flask 离线安装 Flask 还需要什
  • Wildfly 中有多个持久单元?

    Wildfly 9 0 2 应用程序中是否可以有两个持久性单元 我收到 WFLYJPA0061 未指定持久性单元名称 并且应用程序部署部署 jasper web war 中有 2 个持久性单元定义 要么将应用程序部署更改为只有一个持久性单元
  • Java 中使用分隔符“.”的分词问题

    我需要使用分隔符分割文本 例如我想要这个字符串 Washington is the U S Capital Barack is living there 分为两部分 Washington is the U S Capital Barack
  • Spring Security 中具有密码授予的 oAuth2 客户端

    我正在使用一组受 oAuth2 保护的服务 目前的工作原理如下 客户端使用用户名和密码登录 我用这些换取代币 我将令牌保留在会话中 并在每次想要调用服务时提交它 它可以工作 但问题是我完全手动执行此操作 而没有使用 Spring Secur