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

Spring Security OAuth2(google)web应用程序在重定向循环中

阎阳
2023-03-14

我正在尝试构建一个Spring MVC应用程序,并使用Spring Security OAuth2保护它,提供商是Google。我能够使web应用程序在没有安全和表单登录的情况下工作。然而,我不能让谷歌的OAuth工作。谷歌应用程序设置是好的,因为我可以得到回调等工作与一个非Spring Security应用程序。

我的安全配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:sec="http://www.springframework.org/schema/security"
         xmlns:b="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
    <sec:http use-expressions="true" entry-point-ref="clientAuthenticationEntryPoint">
        <sec:http-basic/>
        <sec:logout/>
        <sec:anonymous enabled="false"/>

        <sec:intercept-url pattern="/**" access="isFullyAuthenticated()"/>

        <sec:custom-filter ref="oauth2ClientContextFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
        <sec:custom-filter ref="googleAuthenticationFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
    </sec:http>

    <b:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>

    <sec:authentication-manager alias="alternateAuthenticationManager">
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="user" password="password" authorities="DOMAIN_USER"/>
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</b:beans>
@Configuration
@EnableOAuth2Client
class ResourceConfiguration {
    @Autowired
    private Environment env;

    @Resource
    @Qualifier("accessTokenRequest")
    private AccessTokenRequest accessTokenRequest;

    @Bean
    public OAuth2ProtectedResourceDetails googleResource() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setId("google-app");
        details.setClientId(env.getProperty("google.client.id"));
        details.setClientSecret(env.getProperty("google.client.secret"));
        details.setAccessTokenUri(env.getProperty("google.accessTokenUri"));
        details.setUserAuthorizationUri(env.getProperty("google.userAuthorizationUri"));
        details.setTokenName(env.getProperty("google.authorization.code"));
        String commaSeparatedScopes = env.getProperty("google.auth.scope");
        details.setScope(parseScopes(commaSeparatedScopes));
        details.setPreEstablishedRedirectUri(env.getProperty("google.preestablished.redirect.url"));
        details.setUseCurrentUri(false);
        details.setAuthenticationScheme(AuthenticationScheme.query);
        details.setClientAuthenticationScheme(AuthenticationScheme.form);
        return details;
    }

    private List<String> parseScopes(String commaSeparatedScopes) {
        List<String> scopes = newArrayList();
        Collections.addAll(scopes, commaSeparatedScopes.split(","));
        return scopes;
    }

    @Bean
    public OAuth2RestTemplate googleRestTemplate() {
        return new OAuth2RestTemplate(googleResource(), new DefaultOAuth2ClientContext(accessTokenRequest));
    }

    @Bean
    public AbstractAuthenticationProcessingFilter googleAuthenticationFilter() {
        return new GoogleOAuthentication2Filter(new GoogleAppsDomainAuthenticationManager(), googleRestTemplate(), "https://accounts.google.com/o/oauth2/auth", "http://localhost:9000");
    }
}
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        try {
            logger.info("OAuth2 Filter Triggered!! for path {} {}", request.getRequestURI(), request.getRequestURL().toString());
            logger.info("OAuth2 Filter hashCode {} request hashCode {}", this.hashCode(), request.hashCode());
            String code = request.getParameter("code");
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            logger.info("Code is {} and authentication is {}", code, authentication == null ? null : authentication.isAuthenticated());
            // not authenticated
            if (requiresRedirectForAuthentication(code)) {
                URI authURI = new URI(googleAuthorizationUrl);

                logger.info("Posting to {} to trigger auth redirect", authURI);
                String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
                logger.info("Getting profile data from {}", url);
                // Should throw RedirectRequiredException
                oauth2RestTemplate.getForEntity(url, GoogleProfile.class);

                // authentication in progress
                return null;
            } else {
                logger.info("OAuth callback received");
                // get user profile and prepare the authentication token object.

                String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
                logger.info("Getting profile data from {}", url);
                ResponseEntity<GoogleProfile> forEntity = oauth2RestTemplate.getForEntity(url, GoogleProfile.class);
                GoogleProfile profile = forEntity.getBody();

                CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(profile.getEmail());
                authenticationToken.setAuthenticated(false);
                Authentication authenticate = getAuthenticationManager().authenticate(authenticationToken);
                logger.info("Final authentication is {}", authenticate == null ? null : authenticate.isAuthenticated());

                return authenticate;
            }
        } catch (URISyntaxException e) {
            Throwables.propagate(e);
        }
        return null;
    }
o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'metricFilter' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'oauth2ClientContextFilter' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'googleOAuthFilter' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.filterChainProxy' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'applicationContextIdFilter' to: [/*] 
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'webRequestLoggingFilter' to: [/*] 

对Google的重定向工作良好,我得到了对过滤器的回调,并且验证成功。然而,在此之后,请求导致重定向,并且它再次调用过滤器(请求是相同的,我已经检查了hasCode)。第二次调用时,SecurityContext中的身份验证为Null。作为第一个身份验证调用的一部分,身份验证对象是在安全上下文中填充的,那么为什么它会消失呢?我是第一次使用Spring Security,所以可能犯了新手错误。

共有1个答案

隆璞
2023-03-14

在使用了Spring Security配置和过滤器之后,我终于能够实现这个功能了。我不得不做一些重要的改变

  • 我使用了标准Spring OAuth2筛选器(org.SpringFramework.Security.OAuth2.Client.filter.Oauth2ClientAuthenticationProcessingFilter)而不是我正在使用的自定义筛选器。
  • 将身份验证筛选器的截取URL更改为/GoogleLogin并添加一个身份验证入口点,该入口点在身份验证失败时重定向到此URL。

总体流程如下

  • 浏览器访问/,请求通过Oauth2ClientContextFilterOauth2ClientAuthenticationProcessingFilter,因为上下文不匹配。为登录配置的上下文路径是/GoogleLogin
  • 安全侦听器FilterSecurityInterceptor检测到用户是匿名的,并引发拒绝访问异常。
  • Spring Security的ExceptionTranslationFilter捕获拒绝访问异常,并请求配置的身份验证入口点处理该异常,从而向/GoogleLogin发出重定向。
  • 对于请求/GoogleLogin,筛选器Oauth2AuthenticationProcessingFilter尝试访问Google受保护的资源,并抛出UserRedirectRequiredException,该Oauth2ClientContextFilter将其转换为到Google的HTTP重定向(带有OAuth2详细信息)。
  • 在Google验证成功后,浏览器将用OAuth代码重定向回/GoogleLogin。筛选器oauth2authenticationprocessingfilter将处理此问题,并创建authentication对象,并更新securitycontext
  • 此时,用户被完全验证,并被重定向到oauth2authenticationprocessingfilter
  • FilterSecurityInterceptor允许请求继续,因为SecurityContext包含一个经过身份验证的身份验证对象
  • 最后,将呈现使用isfullyauthenticated()或类似表达式保护的应用程序页。

安全上下文xml如下所示:

<sec:http use-expressions="true" entry-point-ref="clientAuthenticationEntryPoint">
    <sec:http-basic/>
    <sec:logout/>
    <sec:anonymous enabled="false"/>

    <sec:intercept-url pattern="/**" access="isFullyAuthenticated()"/>

    <!-- This is the crucial part and the wiring is very important -->
    <!-- 
        The order in which these filters execute are very important. oauth2ClientContextFilter must be invoked before 
        oAuth2AuthenticationProcessingFilter, that's because when a redirect to Google is required, oAuth2AuthenticationProcessingFilter 
        throws a UserRedirectException which the oauth2ClientContextFilter handles and generates a redirect request to Google.
        Subsequently the response from Google is handled by the oAuth2AuthenticationProcessingFilter to populate the 
        Authentication object and stored in the SecurityContext
    -->
    <sec:custom-filter ref="oauth2ClientContextFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
    <sec:custom-filter ref="oAuth2AuthenticationProcessingFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>

<b:bean id="oAuth2AuthenticationProcessingFilter" class="org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter">
    <b:constructor-arg name="defaultFilterProcessesUrl" value="/googleLogin"/>
    <b:property name="restTemplate" ref="googleRestTemplate"/>
    <b:property name="tokenServices" ref="tokenServices"/>
</b:bean>

<!--
    These token classes are mostly a clone of the Spring classes but have the structure modified so that the response
    from Google can be handled.
-->
<b:bean id="tokenServices" class="com.rst.oauth2.google.security.GoogleTokenServices">
    <b:property name="checkTokenEndpointUrl" value="https://www.googleapis.com/oauth2/v1/tokeninfo"/>
    <b:property name="clientId" value="${google.client.id}"/>
    <b:property name="clientSecret" value="${google.client.secret}"/>
    <b:property name="accessTokenConverter">
        <b:bean class="com.rst.oauth2.google.security.GoogleAccessTokenConverter">
            <b:property name="userTokenConverter">
                <b:bean class="com.rst.oauth2.google.security.DefaultUserAuthenticationConverter"/>
            </b:property>
        </b:bean>
    </b:property>
</b:bean>

<!-- 
    This authentication entry point is used for all the unauthenticated or unauthorised sessions to be directed to the 
    /googleLogin URL which is then intercepted by the oAuth2AuthenticationProcessingFilter to trigger authentication from 
    Google.
-->
<b:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <b:property name="loginFormUrl" value="/googleLogin"/>
</b:bean>

另外,OAuth2资源的Java配置如下所示:

@Configuration
@EnableOAuth2Client
class OAuth2SecurityConfiguration {
    @Autowired
    private Environment env;

    @Resource
    @Qualifier("accessTokenRequest")
    private AccessTokenRequest accessTokenRequest;

    @Bean
    @Scope("session")
    public OAuth2ProtectedResourceDetails googleResource() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setId("google-oauth-client");
        details.setClientId(env.getProperty("google.client.id"));
        details.setClientSecret(env.getProperty("google.client.secret"));
        details.setAccessTokenUri(env.getProperty("google.accessTokenUri"));
        details.setUserAuthorizationUri(env.getProperty("google.userAuthorizationUri"));
        details.setTokenName(env.getProperty("google.authorization.code"));
        String commaSeparatedScopes = env.getProperty("google.auth.scope");
        details.setScope(parseScopes(commaSeparatedScopes));
        details.setPreEstablishedRedirectUri(env.getProperty("google.preestablished.redirect.url"));
        details.setUseCurrentUri(false);
        details.setAuthenticationScheme(AuthenticationScheme.query);
        details.setClientAuthenticationScheme(AuthenticationScheme.form);
        return details;
    }

    private List<String> parseScopes(String commaSeparatedScopes) {
        List<String> scopes = newArrayList();
        Collections.addAll(scopes, commaSeparatedScopes.split(","));
        return scopes;
    }

    @Bean
    @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
    public OAuth2RestTemplate googleRestTemplate() {
        return new OAuth2RestTemplate(googleResource(), new DefaultOAuth2ClientContext(accessTokenRequest));
    }
}

我不得不重写一些Spring类,因为来自Google的令牌的格式和Spring期望的格式不匹配。所以那里需要一些定制的手工。

 类似资料:
  • 问题内容: 在我正在开发的应用程序上,我已经花了好一阵子了。经过数小时的尝试,调试了一个问题,该问题是接口锁定,什么也没发生,我发现这是可怕的While循环。请参阅下面的示例并运行它。通过单击按钮启动while循环时,您将无法在屏幕上执行任何其他操作。在这种情况下,只需按下一个简单的警报按钮即可。 如何有一个循环需要运行直到计数完成并且仍然能够在我的应用程序中执行其他任务?我还应该注意,我已经使用

  • 我找不到我的谜题。当我加载页面时,chrome会说:localhost页面不工作,localhost重定向了你太多次。我猜这是在创造一个循环。我可能做错了一些我无法得到的事情。这是我的密码。 授权控制器 模型 Authenticate.php config/auth.php

  • 在Android设备上,打开Google Play上应用程序的链接: https://play.google.com/store/apps/details?id=com.rovio.angrybirds 默认情况下,将自动打开Google Play应用程序。 但是,如果您有重定向到Google Play链接的链接,设备将打开浏览器,然后导航到浏览器版本的Google Play。为什么会出现这种行为

  • 我有一个小难题,我正在尝试建立一个登录系统,它使用laravel auth脚手架将普通用户与管理员用户区分开来。 问题是它在中间件中处于无限重定向循环中。 在我按下登录按钮后,它会不断重定向到一条路线,问题是,我如何才能用“laravel方法”或任何其他方法解决这个问题。 这是我的控制器:1。基本家庭控制器: 主管理控制器-入口控制器: 登录控制器(默认的一个从auth scaffolding-由

  • 我有这个应用程序工作在一个标准的LAMP堆栈,但当我试图运行它在docker nginx php-fpm我得到一个错误(使用richarove/nginx-php-fpm. docker容器)。 编辑:此容器在同一容器中运行nginx和php fpm。 http://ip-vm/sistema/index.php/gui/gui/login 这是标准输出错误: 2016/04/13 23:23:1

  • 问题内容: 我们在办公室内部有一个可以通过Java Web访问的应用程序。 该应用程序生成的所有日志都显示在Firefox的控制台中。 我想要做的是将其重定向到文件。 那可能吗 ? 以及如何做到这一点? PS:该应用程序的设计如下:客户端应用程序,用于访问服务器上的EJB。 客户端部分是通过java webstart提供的部分。 问题答案: 我认为使用System.setOut方法可以将所有Sys