【问题标题】:Generate nonce in an Spring Security application using OpenID connect使用 OpenID 连接在 Spring Security 应用程序中生成随机数
【发布时间】:2018-09-04 15:36:28
【问题描述】:

我正在将 Spring 安全应用程序插入 IDP/OP(IDentity P提供者,或 Openid connect Identity P提供者根据 OpenID 连接术语)

我正在使用授权代码流。 我使用这个实现来启动我的代码: https://github.com/gazbert/openid-connect-spring-client

它与多个 IDP 一起工作,直到我找到一个需要 nonce 参数的 IDP。 但是我无法设法将我的应用程序配置为生成随机数,并将其添加到 url 中(我知道这是随机数,因为当我手动添加它时:它可以工作)

当应用程序将用户重定向到我希望有一个随机数的 IDP(授权端点)时。 如果可以在返回时验证随机数,那将是完美的。

我在网上搜索了 2 个小时,我发现这可能是要使用的东西 org.springframework.security.oauth.provider.nonce 但没有找到任何示例,或关于如何在我的代码中添加它的线索

这是代码中有趣的部分,我认为我必须告诉 Spring 使用 nonce:

   public OAuth2RestTemplate getOpenIdConnectRestTemplate(@Qualifier("oauth2ClientContext")
                                                                         OAuth2ClientContext clientContext) {
        return new OAuth2RestTemplate(createOpenIdConnectCodeConfig(), clientContext);

    }



    public OAuth2ProtectedResourceDetails createOpenIdConnectCodeConfig() {
        final AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
        resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.form); // include client credentials in POST Content
        resourceDetails.setClientId(clientId);
        resourceDetails.setClientSecret(clientSecret);
        resourceDetails.setUserAuthorizationUri(authorizationUri);
        resourceDetails.setAccessTokenUri(tokenUri);

        final List<String> scopes = new ArrayList<>();
        scopes.add("openid"); // always need this
        scopes.addAll(Arrays.asList(optionalScopes.split(",")));
        resourceDetails.setScope(scopes);

        resourceDetails.setPreEstablishedRedirectUri(redirectUri);
        resourceDetails.setUseCurrentUri(false);
        return resourceDetails;
    }

如果有修改,我相信它就在那里。 如果那是重复的,我很抱歉,我再也不会羞辱自己了。

任何帮助将不胜感激,如果需要,我可以发布更多详细信息,我不想通过发布太多来混淆

感谢阅读

【问题讨论】:

  • 究竟是什么不起作用?创建一个nonce应该不是那么难,只需创建一个随机令牌就可以了
  • 嗨,Nico 感谢您的回答,创建随机数很容易,但我不是曾经创建包含随机数的 http 请求的人。春天做到了。在我的示例中,我做了一个 resourceDetails.setClientId(clientId) 和 Spring 在 URL 中添加 clientID。我无法直接访问发出的 url,我希望 Spring 添加它。 PS:我检查了方法列表,没有“激活随机数功能”
  • 如果有足够权限的人可以编辑我的错字:“但我不是 onCe 创造者”。 =>“但我不是创造者”。并删除此评论,那会很好。我从事“nonce”业务的时间太长了。谢谢
  • 我的朋友运气好吗?
  • 不,我离开了那部分,并告诉我的老板,使用原生 Spring 实现是不可能的

标签: spring-security openid-connect


【解决方案1】:

我也为此苦苦挣扎。幸运的是,Spring Security documentation 最近有一些进展,在与一位 GitHub 开发人员来回折腾之后,我想出了一个 Kotlin 的解决方案(翻译成 Java 应该相当容易)。原始讨论可以在here找到。

最终,我的SecurityConfig 班级最终看起来像这样:

@EnableWebSecurity
class SecurityConfig @Autowired constructor(loginGovConfiguration: LoginGovConfiguration) : WebSecurityConfigurerAdapter() {

    @Autowired
    lateinit var clientRegistrationRepository: ClientRegistrationRepository

    private final val keystore: MutableMap<String, String?> = loginGovConfiguration.keystore
    private final val keystoreUtil: KeystoreUtil = KeystoreUtil(
            keyStore = keystore["file"],
            keyStorePassword = keystore["password"],
            keyAlias = keystore["alias"],
            keyPassword = null,
            keyStoreType = keystore["type"]
    )
    private final val allowedOrigin: String = loginGovConfiguration.allowedOrigin

    companion object {
        const val LOGIN_ENDPOINT = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL
        const val LOGIN_SUCCESS_ENDPOINT = "/login_success"
        const val LOGIN_FAILURE_ENDPOINT = "/login_failure"
        const val LOGIN_PROFILE_ENDPOINT = "/login_profile"
        const val LOGOUT_ENDPOINT = "/logout"
        const val LOGOUT_SUCCESS_ENDPOINT = "/logout_success"
    }

    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
            // login, login failure, and index are allowed by anyone
            .antMatchers(
                    LOGIN_ENDPOINT,
                    LOGIN_SUCCESS_ENDPOINT,
                    LOGIN_PROFILE_ENDPOINT,
                    LOGIN_FAILURE_ENDPOINT,
                    LOGOUT_ENDPOINT,
                    LOGOUT_SUCCESS_ENDPOINT,
                    "/"
            )
                .permitAll()
            // any other requests are allowed by an authenticated user
            .anyRequest()
                .authenticated()
            .and()
            // custom logout behavior
            .logout()
                .logoutRequestMatcher(AntPathRequestMatcher(LOGOUT_ENDPOINT))
                .logoutSuccessUrl(LOGOUT_SUCCESS_ENDPOINT)
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .logoutSuccessHandler(LoginGovLogoutSuccessHandler())
            .and()
            // configure authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider
            .oauth2Login()
                .authorizationEndpoint()
                .authorizationRequestResolver(LoginGovAuthorizationRequestResolver(clientRegistrationRepository))
                .authorizationRequestRepository(authorizationRequestRepository())
                .and()
                .tokenEndpoint()
                .accessTokenResponseClient(accessTokenResponseClient())
                .and()
                .failureUrl(LOGIN_FAILURE_ENDPOINT)
                .successHandler(LoginGovAuthenticationSuccessHandler())
    }

    @Bean
    fun corsFilter(): CorsFilter {
        // fix OPTIONS preflight login profile request failure with 403 Invalid CORS request
        val config = CorsConfiguration()
        config.addAllowedOrigin(allowedOrigin)
        config.allowCredentials = true
        config.allowedHeaders = listOf("x-auth-token", "Authorization", "cache", "Content-Type")
        config.addAllowedMethod(HttpMethod.OPTIONS)
        config.addAllowedMethod(HttpMethod.GET)

        val source = UrlBasedCorsConfigurationSource()
        source.registerCorsConfiguration(LOGIN_PROFILE_ENDPOINT, config)

        return CorsFilter(source)
    }

    @Bean
    fun authorizationRequestRepository(): AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
        return HttpSessionOAuth2AuthorizationRequestRepository()
    }

    @Bean
    fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
        val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
        accessTokenResponseClient.setRequestEntityConverter(LoginGovTokenRequestConverter(clientRegistrationRepository, keystoreUtil))
        return accessTokenResponseClient
    }
}

还有我的自定义授权解析器LoginGovAuthorizationRequestResolver

class LoginGovAuthorizationRequestResolver(clientRegistrationRepository: ClientRegistrationRepository) : OAuth2AuthorizationRequestResolver {

    private val REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"
    private var defaultAuthorizationRequestResolver: OAuth2AuthorizationRequestResolver = DefaultOAuth2AuthorizationRequestResolver(
            clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
    )
    private val authorizationRequestMatcher: AntPathRequestMatcher = AntPathRequestMatcher(
            OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}")

    override fun resolve(request: HttpServletRequest?): OAuth2AuthorizationRequest? {
        val authorizationRequest: OAuth2AuthorizationRequest? = defaultAuthorizationRequestResolver.resolve(request)
        return if(authorizationRequest == null)
        { null } else { customAuthorizationRequest(authorizationRequest) }
    }

    override fun resolve(request: HttpServletRequest?, clientRegistrationId: String?): OAuth2AuthorizationRequest? {
        val authorizationRequest: OAuth2AuthorizationRequest? = defaultAuthorizationRequestResolver.resolve(request, clientRegistrationId)
        return if(authorizationRequest == null)
        { null } else { customAuthorizationRequest(authorizationRequest) }
    }

    private fun customAuthorizationRequest(authorizationRequest: OAuth2AuthorizationRequest?): OAuth2AuthorizationRequest {

        val registrationId: String = this.resolveRegistrationId(authorizationRequest)
        val additionalParameters = LinkedHashMap(authorizationRequest?.additionalParameters)

        // set login.gov specific params
        // https://developers.login.gov/oidc/#authorization
        if(registrationId == LOGIN_GOV_REGISTRATION_ID) {
            additionalParameters["nonce"] = "1234567890" // generate your nonce here (should actually include per-session state and be unguessable)
            // add other custom params...
        }

        return OAuth2AuthorizationRequest
            .from(authorizationRequest)
            .additionalParameters(additionalParameters)
            .build()
    }

    private fun resolveRegistrationId(authorizationRequest: OAuth2AuthorizationRequest?): String {
        return authorizationRequest!!.additionalParameters[OAuth2ParameterNames.REGISTRATION_ID] as String
    }

}

【讨论】:

  • 它看起来不错,我现在无法测试它,但我会支持它,一旦它被验证,我会再次发表评论,并放入 java 代码。非常感谢您的帮助!
  • @tritonoidc 为了更好地理解这些东西,我在集成到我的官方和现有 API 之前做了一个玩具项目。看看引用它是否可以帮助你。到目前为止,它仍然缺少正确处理 nonce 验证等内容,但这应该很快就会出现:github.com/forgo/login-gov
  • 此外,为了清晰起见,我根据我在项目中添加和修复的内容对我的原始答案进行了一些编辑,以便将来偶然发现这个答案的人。如果它适合您,请务必标记此答案。
  • 感谢链接,听起来不错,我可能在 2 月之前没有空闲时间进行测试
猜你喜欢
  • 1970-01-01
  • 2020-01-14
  • 2017-02-24
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-20
  • 1970-01-01
相关资源
最近更新 更多