使用OpenID connect在Spring Security应用程序中生成nonce

时间:2018-03-26 13:12:39

标签: spring-security openid-connect

我将Spring安全应用程序插入IDP / OP( IDe ntity P rovider,或 O penid连接身份 P rovider根据OpenID连接术语)

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

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

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

我在网上搜索了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;
    }

如果有修改我相信它就在那里。 如果这是重复的我道歉,我永远不会再羞辱自己。

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

感谢您的阅读

1 个答案:

答案 0 :(得分: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
    }

}