当前位置: 首页 > 知识库问答 >
问题:

如何使用Spring Security OAuth2要求每个唯一匿名用户同意?

邴英毅
2023-03-14

我的应用程序有一个单一的endpoint。它触发OAuth2授权授予流。它只能由匿名用户调用。每个匿名用户代表在资源服务器中具有不同权限的不同人员。需要每个匿名用户的同意(即不同的授权)。

Spring Boot OAuth2中需要每个匿名用户同意的配置是什么?

我正在使用Spring Boot oath2-Client 2.6.4和Spring Security 5.6.2。

目前,我有oauth2client配置。它不符合要求。在此配置中,只请求一次同意,并将其应用于以下所有匿名呼叫者。所有呼叫者共享相同的授权和访问令牌。

我觉得oauth2login可能是合适的配置,但在尝试oauth2login之前,我必须克服一些必要的定制。我必须禁用生成的登录页面,该页面提示用户选择提供者,并且我必须向授权请求中添加自定义字段。我在outh2login中的这些定制没有成功。因此,这种方法感觉不错,但似乎不可用。

有关此endpoint调用者的信息,请参阅:HL7 FHIR SMART-APP-LAUNCH

共有1个答案

暴英达
2023-03-14

这方面存在许多挑战,涉及:

我的应用程序有一个单一的endpoint。[...]它只能由匿名用户调用。

这一要求使得Spring Security很难提供太多帮助。这是因为匿名用户通常没有会话,授权代码授予是一个需要状态的流,因此需要会话。顺便说一句,我不确定我是否完全理解您链接到的规范(据我所知,它构建在OAuth 2.0上)在允许匿名用户的客户端环境中是如何或为什么有意义的。

尽管如此,这似乎只需要使用<代码>。如果您创建用于管理匿名用户的自定义过滤器,则oauth2Client()支持Spring Security。注意:以下假设即使浏览器中存在会话,授权服务器也不会忽略启动参数。

以下配置定义和配置此过滤器,以及自定义oauth2Client()以将启动参数传递给授权服务器。它本质上为启动参数创建了一个临时身份验证,该参数将在流期间保存为会话中的principalName。

@EnableWebSecurity
public class SecurityConfig {

    private static final String PARAMETER_NAME = "launch";

    private static final String ROLE_NAME = "LAUNCH_USER";

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().hasRole(ROLE_NAME)
            )
            .addFilterAfter(authenticationFilter(), AnonymousAuthenticationFilter.class)
            .oauth2Client((oauth2) -> oauth2
                .authorizationCodeGrant((authorizationCode) -> authorizationCode
                    .authorizationRequestResolver(authorizationRequestResolver(clientRegistrationRepository))
                )
            );
        return http.build();
    }

    private OAuth2AuthorizationRequestResolver authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
        DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
                new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);

        // Configure a request customizer for the OAuth2AuthorizationRequestRedirectFilter
        authorizationRequestResolver.setAuthorizationRequestCustomizer((authorizationRequest) -> {
            Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();

            // Customize request with principal name originally obtained from request parameter
            if (currentAuthentication instanceof RequestParameterAuthenticationToken) {
                Map<String, Object> additionalParameters = Map.of(PARAMETER_NAME, currentAuthentication.getName());
                authorizationRequest.additionalParameters(additionalParameters);
            }
        });

        return authorizationRequestResolver;
    }

    private RequestParameterAuthenticationFilter authenticationFilter() {
        return new RequestParameterAuthenticationFilter(PARAMETER_NAME, AuthorityUtils.createAuthorityList("ROLE_" + ROLE_NAME));
    }

    /**
     * Authentication filter that authenticates an anonymous request using a request parameter.
     */
    public static final class RequestParameterAuthenticationFilter extends OncePerRequestFilter {

        private final String parameterName;

        private final List<GrantedAuthority> authorities;

        public RequestParameterAuthenticationFilter(String parameterName, List<GrantedAuthority> authorities) {
            this.parameterName = parameterName;
            this.authorities = authorities;
        }

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            SecurityContext existingSecurityContext = SecurityContextHolder.getContext();
            if (existingSecurityContext != null && !(existingSecurityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
                filterChain.doFilter(request, response);
                return;
            }

            String principalName = request.getParameter(parameterName);
            if (principalName != null) {
                Authentication authenticationResult = new RequestParameterAuthenticationToken(principalName, authorities);
                authenticationResult.setAuthenticated(true);

                SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
                securityContext.setAuthentication(authenticationResult);
                SecurityContextHolder.setContext(securityContext);
            }
            filterChain.doFilter(request, response);
        }

    }

    /**
     * Custom authentication token that can be persisted between requests, but is otherwise very similar to
     * {@link AnonymousAuthenticationToken}.
     */
    public static final class RequestParameterAuthenticationToken extends AbstractAuthenticationToken implements Serializable {

        private static final long serialVersionUID = 1L;

        private final String principalName;

        public RequestParameterAuthenticationToken(String principalName, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principalName = principalName;
        }

        @Override
        public Object getPrincipal() {
            return this.principalName;
        }

        @Override
        public Object getCredentials() {
            return this.principalName;
        }

    }

}

您可以在控制器endpoint中使用它,如以下示例所示:

java prettyprint-override">@RestController
public class LaunchController {

    @GetMapping("/app/launch")
    public void launch(
            @RegisteredOAuth2AuthorizedClient("fhir-client")
                    OAuth2AuthorizedClient authorizedClient) {
        String launchParameter = authorizedClient.getPrincipalName();
        String accessToken = authorizedClient.getAccessToken().getTokenValue();
        // Use authorizedClient.getAccessToken() to make a request (WebClient)...

        // Clear the SecurityContext after the request, to force the next request
        // to start the flow over again
        SecurityContextHolder.clearContext();
    }

}

有关此答案的其他上下文,请参阅相关问题#11069。

 类似资料:
  • Parse将在年底关闭,所以我决定开始使用Firebase。我需要用3个字段实现注册过程:电子邮件、用户名、密码(电子邮件 由于Firebase没有提供像Parse这样管理用户名的简单方法,我决定只使用电子邮件/密码注册并保存一些附加数据,例如用户名。这是我的用户数据结构: 但是,我想做的是使用户名唯一,并在创建帐户之前进行检查。以下是我的规则: 非常感谢您的帮助

  • 我正在使用SoapUI的开源版本进行一些SOAP Web服务负载测试。 我希望每个请求与以前的请求尽可能不同,我不希望使用相同的属性进行负载测试。 我有一个属性文件,它有几个属性,每个属性值都调用groovy脚本从文件中读取一个随机值,并将其分配给测试用例属性,即在值字段中: <代码>${=(DynamicPropertyScript)} 脚本: 如果我在测试用例开始时调用这个脚本,那么这个脚本工

  • 问题内容: 我正在使用任何具有唯一性的应用程序,每当最终用户输入他的应用程序时,我都会通过Ajax发出SQL查询,以检查是否存在该查询。在此之前,它工作正常,但现在我知道那是什么时候存在,那么我想在JSP页面上显示该ID已被使用,反之亦然。 所以我的问题是如何从struts应用程序获取响应并将其提供给Ajax或JSP页面,以及在“ struts.xml”结果标签中写入什么? 这是我的Ajax呼叫:

  • 我有一个注册表格,通过以下步骤: 填写用户信息(姓名,地址等)和用户名 如果用户按下提交按钮,则检查用户名是否已经在数据库中 如果用户名已经在使用,他必须重命名用户名,如果没有,则显示他输入的内容(名称,地址等)。 他必须确认网站规则和他输入的数据是正确的。如果他按下确认按钮,数据库中将插入一行他的详细信息和用户名。 我不想在步骤2之后将行插入数据库中。,因为我不希望数据库中没有确认规则的用户的行

  • 每当我试图进入我的repo-git时,它都会同时请求

  • 在上面的代码示例中,试图为每个请求生成唯一的txn-id。但是,观察到它正在重复使用相同的数字导致重复。我还尝试使用以下方法根据当前时间生成,当在1秒内触发超过1个请求时,它仍然会导致重复。 任何替代解决方案来为每个请求生成唯一ID,而不管并发情况如何? 提前感谢。