Spring Oauth2: Redirect back to origin url after login successfully.

Spring OAuth2 登录成功后跳转到原来的地址

本文介绍基于Spring Cloud Zuul实现的OAuth2 Clinet在跳转到OAuth2 Server的登录页面成功登录后如何redirect跳转回原来的地址,即redirect back to origin url after login successfully.

Spring OAuth SSO的基本过程有以下几步:

  1. 用户访问网站,打开了一个链接(origin url) ;
  2. 访问请求发送给Zuul Gateway服务器(该服务器同时也是OAuth2 Client),服务器判断该请求是否访问了受保护的资源;
  3. 如果是访问受保护的资源则判断登录状态,没有登录则重定向到OAuth2 Server的登录页面;
  4. 用户输入账户信息进行登录,登录成功后将用户信息保持在Spring Security Context中并进行页面跳转。

由于之前系统框架中只有一个客户端,而且我们的客户端是基于Vue的单页面应用,所以就在OAuth2 Server中直接配置了一个登录成功后的默认地址。最近需要增加一个客户端,在使用同一个OAuth2 Server作为统一登录时就面临着必须让用户从哪里来回哪里去的问题!经过查阅文档和跟踪调试,OAuth2 Client在检测到用户未登录访问受保护的资源时会直接redirectOAuth2 Server,redirect的url是基于OAuth2 Client配置的security.oauth2.client.user-authorization-urisecurity.oauth2.client.client-id进行拼接而成的,如下所示:

http://127.0.0.1:8080/auth/oauth/authorize?client_id=oauth2_client_id&redirect_uri=http://127.0.0.1/login&response_type=code&state=B5c3xa

除了这段redirect url中的数据之外,OAuth2 Client没有再给OAuth2 Server任何其他数据,所以我需要将origin url作为query string参数放到这段redirect url里面去。解决方案分为三步:

  1. OAuth2 Client中获取到origin url并在组装oauth2的redirect url时添加到query string中传递给OAuth2 Server
  2. OAuth2 Server在自己的LoginSuccessHandler中从request session中拿出SPRING_SECURITY_SAVED_REQUEST获取到redirect url并解析出original url
  3. response.sendRedirect(originUrl)

基于spring cloud zuul gatewayOAuth2 Client配置:

  1. 重写LoginUrlAuthenticationEntryPointorigin url保存在session中;
  2. 重写DefaultRedirectStrategysession中的origin url拼接在redirect url
/**
 * UI代理服务器,基于zuul的 oauth2 client.
 */
@SpringBootApplication
@EnableOAuth2Sso
@EnableZuulProxy
public class UIProxyApplication extends WebSecurityConfigurerAdapter{

    public static void main(String[] args) {
        SpringApplication.run(UIProxyApplication.class, args);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println(logoutUrl);
        http.exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint("/login"));
        http.authorizeRequests()
            .antMatchers("/login", "/api/**").permitAll()
            .anyRequest().authenticated()
            .and().csrf().disable()
    }

    @Bean
    public LogoutHandler logoutHandler() {
        return new MyLogoutHandler();
    }

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

    class OAuthRedirectStrategy extends DefaultRedirectStrategy {
        @Override
        public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
            String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
            redirectUrl = response.encodeRedirectURL(redirectUrl);
            if (logger.isDebugEnabled()) {
                logger.debug("Custom BMA SecurityConfiguration Redirecting to '" + redirectUrl + "'");
            }

            String requestUrl = request.getSession().getAttribute("requestUrl").toString();
            redirectUrl += "&request_url=" + requestUrl;
            response.sendRedirect(redirectUrl);
        }
    }

        //转发或者重定向到登录页面
    public class UnauthorizedEntryPoint extends LoginUrlAuthenticationEntryPoint {
        public UnauthorizedEntryPoint(String loginFormUrl) {
            super(loginFormUrl);
        }

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            request.getSession().setAttribute("requestUrl", request.getRequestURL());
            super.commence(request, response, authException);
        }
    }
}

OAuth2 ServerLoginSuccessHandler处理redirect urlredirectorigin url

public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response, Authentication authentication) throws IOException,
            ServletException {

        Object principal = authentication.getPrincipal();
        String username = "";
        if (principal instanceof UserDetails) {
            username = ((UserDetails) principal).getUsername();
        } else {
            username = principal.toString();
        }

        if (request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST") != null) {
            String savedRequest = request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST").toString();
            String[] params = savedRequest.split("&");
            for (int i = 0; i < params.length; i++) {
                if (params[i].indexOf("request_url") != -1) {
                    String requestUrl = params[i].split("=")[1].split("]")[0];
                    response.sendRedirect(requestUrl);
                }
            }
        } else {
            super.onAuthenticationSuccess(request, response, token);
        }
    }
}

OAuth2 Server中配置LoginSuccessHandler

/**
 * 安全配置类
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        //customize login page.
        httpSecurity
                .authorizeRequests().antMatchers(
                "/login/**",
                "/js/**",
                "/css/**",
                "/img/**").permitAll()
                .anyRequest().authenticated()
                .and().formLogin().loginPage("/login").permitAll()
                .and().csrf().disable()

        httpSecurity.addFilter(myUsernamePasswordAuthenticationFilter());
    }

    @Bean
    FilterRegistrationBean forwardedHeaderFilter() {
        FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
        filterRegBean.setFilter(new ForwardedHeaderFilter());
        filterRegBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return filterRegBean;
    }

    @Bean
    public UsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception{
        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();

        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(loginSuccessHandler());
        return filter;
    }

    @Bean
    public LoginSuccessHandler loginSuccessHandler() {
        return new LoginSuccessHandler();
    }

}

一些说明:

  1. OAuth2是基于JWT 的,相关配置没有在这里给出;
  2. 增加UsernamePasswordAuthenticationFilterLoginSuccessHandler是原有项目需求,不是为了这次的跳转才加的;
  3. 这个方法可能还不是common practise,有更好的方法欢迎交流!
  4. 参考链接: https://stackoverflow.com/questions/51456479/stateless-spring-jwt-application-enableoauth2client
  5. https://segmentfault.com/a/1190000012137647
  6. https://blog.csdn.net/honghailiang888/article/details/52679264
  7. https://www.baeldung.com/spring-redirect-and-forward

你可能感兴趣的:(软件工程师之路,Java,Spring)