簡體   English   中英

Spring oauth2 不重定向到原始 url

[英]Spring oauth2 dont redirect to original url

我嘗試將授權代碼流配置為客戶端。 就流程而言。 我重定向到登錄頁面。 oauth2 服務器給了我一個授權碼,我可以用這個碼來交換訪問令牌。

但我無法正確完成最后一步:回到原始資源。 這是我的安全配置:

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecureConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

    @Value("${openId.userinfo}")
    private String userInfoUri;

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

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

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

    @Value("${openId.userAuthorizationUri}")
    private String userAuthorizationUri;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
                http.csrf().disable()
                .addFilterAfter(ssoFilter(), BasicAuthenticationFilter.class);
    }

    private OAuth2ClientAuthenticationProcessingFilter ssoFilter() {
        OAuth2ClientAuthenticationProcessingFilter openIDFilter = new OAuth2ClientAuthenticationProcessingFilter("/resource/**");
        openIDFilter.setRestTemplate(restTemplate());
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
        tokenServices.setRestTemplate(restTemplate());
        openIDFilter.setTokenServices(tokenServices);
        return openIDFilter;
    }

    @Bean
    @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public OAuth2RestTemplate restTemplate() {
        return new OAuth2RestTemplate(protectedResourceDetails(), oauth2ClientContext);
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

    @Bean
    public OAuth2ProtectedResourceDetails protectedResourceDetails() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        details.setUserAuthorizationUri(userAuthorizationUri);
        details.setScope(Arrays.asList("read"));
        details.setUseCurrentUri(true);
        return details;
    }
}

這是我的控制器:

@Controller
@RequestMapping("/resource")
public class TestController {

    @RequestMapping(value = "/test",  method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseStatus(code = HttpStatus.OK)
    public void test(){
        System.out.println("hello world");
    }
}

在最后一步 spring 將我重定向到我的基本網址: 在此處輸入圖片說明

我找到了這個論壇帖子

它建議將請求保存在RequestCache 但是這篇文章大約有 6 年的歷史了,也許 spring 在此期間提供了一個更優雅的解決方案?

編輯:這是我的依賴項:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>1.5.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>
</dependencies>

這是一種解決方法,我不喜歡它。 如果有人找到更好的解決方案,我將不勝感激。

為此,我將保持實現簡單和骯臟。 首先,我實現了UpdateSavedRequestFilter以在 requestCache 中保存請求:

public class UpdateSavedRequestFilter extends OncePerRequestFilter {
    private RequestCache requestCache = new HttpSessionRequestCache();

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String queryString = request.getQueryString();
        if(!StringUtils.contains(queryString, "code") && authentication == null) {
            requestCache.saveRequest(request, response);
        }
        filterChain.doFilter(request, response);
    }
}

它沒有按預期工作,我被重定向到“/resource/test”,但身份驗證過程再次被觸發。 所以我已經實現了我自己的Oauth2Filter 它沒有做太多,我主要從http://www.baeldung.com/spring-security-openid-connect復制代碼我唯一的一點是doFilter方法的擴展,並調用了requiresAuthentication來檢查如果用戶已經通過身份驗證

public class OAuth2Filter extends AbstractAuthenticationProcessingFilter {

    public OAuth2RestOperations restTemplate;

    private UserInfoTokenServices tokenServices;

    public OAuth2Filter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        setAuthenticationManager(new NoopAuthenticationManager());
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        setAuthenticationSuccessHandler(successHandler);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if (requiresAuthentication()) {
            super.doFilter(req, res, chain);
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public Authentication attemptAuthentication(
            HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException{
        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();
        } catch (OAuth2Exception e) {
            throw new BadCredentialsException("Could not obtain access token", e);
        }
        try {
            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
            if (authenticationDetailsSource!=null) {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
                result.setDetails(authenticationDetailsSource.buildDetails(request));
            }
            publish(new AuthenticationSuccessEvent(result));
            return result;
        } catch (InvalidTokenException e) {
            throw new BadCredentialsException("Could not obtain user details from token", e);
        }
    }

    private void publish(ApplicationEvent event) {
        if (eventPublisher!=null) {
            eventPublisher.publishEvent(event);
        }
    }

    private static class NoopAuthenticationManager implements AuthenticationManager {

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
        }

    }

    private boolean requiresAuthentication() {
        Authentication currentUser = SecurityContextHolder.getContext()
                .getAuthentication();

        if (currentUser == null) {
            return true;
        }
        OAuth2AccessToken accessToken = restTemplate.getAccessToken();
        if (accessToken == null) {
            return true;
        }
        return accessToken.isExpired();
    }

    public void setRestTemplate(OAuth2RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setTokenServices(UserInfoTokenServices tokenServices) {
        this.tokenServices = tokenServices;
    }
}

這是我其余的 conig 課程:

@Configuration
@EnableWebSecurity
public class SecureConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private OAuth2RestTemplate restTemplate;

    @Value("${openId.userinfo}")
    private String userInfoUri;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
                .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
                .addFilterBefore(new UpdateSavedRequestFilter(), OAuth2Filter.class);
    }

    @Bean
    public OAuth2Filter openIdConnectFilter() {
        OAuth2Filter filter = new OAuth2Filter("/resource/**");
        filter.setRestTemplate(restTemplate);
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
        tokenServices.setRestTemplate(restTemplate);
        filter.setTokenServices(tokenServices);
        return filter;
    }
}

@Configuration
@EnableOAuth2Client
public class OpenIdConnectConfig {
    @Value("${openId.userinfo}")
    private String userInfoUri;

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

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

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

    @Value("${openId.userAuthorizationUri}")
    private String userAuthorizationUri;


    @Bean
    public OAuth2ProtectedResourceDetails protectedResourceDetails() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        details.setUserAuthorizationUri(userAuthorizationUri);
        details.setScope(Arrays.asList("read"));
        details.setUseCurrentUri(true);
        return details;
    }

    @Bean
    @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public OAuth2RestTemplate restTemplate(OAuth2ClientContext clientContext) {
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(protectedResourceDetails(), clientContext);
        return oAuth2RestTemplate;
    }
}

這就是 AbstractAuthenticationProcessingFilter 所說的OAuth2ClientAuthenticationProcessingFilter擴展。

在此處輸入圖片說明

試試這個:

        private OAuth2ClientAuthenticationProcessingFilter ssoFilter() {
                OAuth2ClientAuthenticationProcessingFilter openIDFilter = new OAuth2ClientAuthenticationProcessingFilter("/resource/**");
                openIDFilter.setRestTemplate(restTemplate());
                UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
                tokenServices.setRestTemplate(restTemplate());
                openIDFilter.setTokenServices(tokenServices);

openIDFilter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());


return openIDFilter;

 }

更新:

這是我的調試日志供您參考。

    - Checking match of request : '/dist/i_do not_exist.html'; against '/favicon.ico'
- Checking match of request : '/dist/i_do not_exist.html'; against '/images/**'
- Checking match of request : '/dist/i_do not_exist.html'; against '/css/**'
- Checking match of request : '/dist/i_do not_exist.html'; against '/maxsession.jsp'
- /dist/i_do not_exist.html at position 1 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
- No HttpSession currently exists
- No SecurityContext was available from the HttpSession: null. A new one will be created.
- /dist/i_do not_exist.html at position 2 of 12 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
- /dist/i_do not_exist.html at position 3 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
- /dist/i_do not_exist.html at position 4 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
- /dist/i_do not_exist.html at position 5 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
- /dist/i_do not_exist.html at position 6 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
- /dist/i_do not_exist.html at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
- /dist/i_do not_exist.html at position 8 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
- /dist/i_do not_exist.html at position 9 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
- /dist/i_do not_exist.html at position 10 of 12 in additional filter chain; firing Filter: 'OAuth2ClientContextFilter'
- /dist/i_do not_exist.html at position 11 of 12 in additional filter chain; firing Filter: 'OpenIdConnectFilter'
- /dist/i_do not_exist.html at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
- Secure object: FilterInvocation: URL: /dist/i_do not_exist.html; Attributes: [isFullyAuthenticated()]
11:43:23.719 [http-nio-8080-exec-3] DEBUG o.a.camel.spring.SpringCamelContext - onApplicationEvent: org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent[source=FilterInvocation: URL: /dist/i_do not_exist.html]
- Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:125)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:94)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:616)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:502)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
- Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@cd8de57]
11:43:25.895 [http-nio-8080-exec-3] DEBUG o.a.camel.spring.SpringCamelContext - onApplicationEvent: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@cd8de57]
- DefaultSavedRequest added to Session: DefaultSavedRequest[http://ecuio197m0221:8080/Pagos/dist/i_do%20not_exist.html]
- Calling Authentication entry point.
- Redirecting to 'http://<server>:<port>/<AppContext>/idp-login;jsessionid=CB7BAACDAEDC3A0E37AD5F75C0E38C26'

我不完全確定這是否涵蓋 OP 的原始場景,因為解決方案可能因不同的身份驗證流程而異。

考慮以下場景。

  1. 瀏覽器客戶端從 Referer http://host/home-page進入 oauth 登錄端點http://host/login
  2. 后端返回 302 並重定向到 IAM 進行身份驗證
  3. 用戶自我驗證並返回授權碼到http://host/login?code=XXX
  4. 后端驗證令牌並將用戶重定向到/

我們想將用戶重定向到原始 Referer 而不是根路徑。

ExceptionTranslationFilter通常負責在拋出身份驗證異常后將請求保存在RequestCache 稍后,在身份驗證后AbstractAuthenticationProcessingFilter使用相同的緩存來檢索保存的請求,以進行正確的重定向。

問題是當我們使用上面的認證流程時,沒有涉及到ExceptionTranslationFilter

我的想法是延長OAuth2ClientContextFilter和使用RequestCache重定向開始之前。 我還需要強制RequestCache保存我們的請求,但使用Referer標頭作為正確的重定向 URL。 我使用請求包裝器來覆蓋原始請求 URL。 可能不是最漂亮的解決方案,但它似乎有效。

public class RequestCacheOAuth2ClientContextFilter extends OAuth2ClientContextFilter {
    private final RequestCache cache = new HttpSessionRequestCache();

    @Override
    protected void redirectUser(UserRedirectRequiredException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
            @Override
            public String getRequestURI() {
                return URI.create(getReferer()).getPath();
            }

            @Override
            public StringBuffer getRequestURL() {
                return new StringBuffer(getReferer());
            }

            private String getReferer() {
                return super.getHeader(HttpHeaders.REFERER);
            }
        };
        cache.saveRequest(wrapper, response);
        super.redirectUser(e, request, response);
    }
}

將此 bean 添加到您的 OAuth 配置中

    @Bean
    @Primary
    public OAuth2ClientContextFilter oAuth2ClientContextFilter() {
        return new RequestCacheOAuth2ClientContextFilter();
    }

如果您使用@EnableOAuth2Client您可能需要使用@Primary

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM