2016-09-13 8 views
0

私は、JASIG-CASサーバーを使用するユーザーを認証し、バックエンドから保護されたリソースにアクセスできるフロントエンドにリダイレクトするスプリングブートバックエンドアプリケーションを使用しています。今私はモバイルクライアントを追加する必要があります。今までの私の構成はとても似CasAuthenticationFilterで私のフロントエンドのハードコードされたURLにSimpleUrlAuthenticationSuccessHandlerを持っていた:2つの異なるSpring Security認証フローを持つCASサーバーの使用

public CasAuthenticationFilter casAuthenticationFilter() throws Exception { 
    CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();  authenticationFilter.setAuthenticationManager(authenticationManager()); 
    authenticationFilter.setServiceProperties(serviceProperties()); 
    authenticationFilter.setFilterProcessesUrl("/auth/cas"); 
    SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler = 
     new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET)); 
    authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler); 
    return authenticationFilter; 
}//CasAuthenticationFilter 

しかし、今、私のモバイルクライアントは、ブラウザ、ショーおなじみのCASのログインページを開き、ユーザーを認証し、発行しますバックエンドにリダイレクトする必要がありますモバイルアプリケーションへのディープリンク。問題は、フロントエンドを指すハードコードされたリダイレクトターゲットです。 CASからのリクエストは、フロントエンドまたはモバイルの両方からブラウザを使用しているためにトリガーされた場合と同じように見えます。したがって、自分でAuthenticationSuccessHandlerを使用して区別することはできません。必死の行為で、同じCASサーバーを使用して異なるコールバックエンドポイントを使用して2つの異なる認証フローを構築しようとしました。ここにこのモンスターがあります: package com.my.company.config;

import org.jasig.cas.client.session.SingleSignOutFilter; 
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.core.env.Environment; 
import org.springframework.http.HttpMethod; 
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; 
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; 
import org.springframework.security.cas.ServiceProperties; 
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; 
import org.springframework.security.cas.authentication.CasAuthenticationProvider; 
import org.springframework.security.cas.authentication.NullStatelessTicketCache; 
import org.springframework.security.cas.web.CasAuthenticationEntryPoint; 
import org.springframework.security.cas.web.CasAuthenticationFilter; 
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; 
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 
import org.springframework.security.config.annotation.web.builders.WebSecurity; 
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 
import org.springframework.security.config.http.SessionCreationPolicy; 
import org.springframework.security.core.SpringSecurityMessageSource; 
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; 
import org.springframework.security.core.userdetails.UserDetailsService; 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 
import org.springframework.security.crypto.password.PasswordEncoder; 
import org.springframework.security.web.DefaultSecurityFilterChain; 
import org.springframework.security.web.FilterChainProxy; 
import org.springframework.security.web.SecurityFilterChain; 
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 

import javax.inject.Inject; 
import javax.servlet.*; 
import javax.servlet.http.Cookie; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpSession; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.List; 

@Configuration 
@EnableWebSecurity 
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 


    private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); 

    private static final String CAS_URL_SERVER = "cas.url.server"; 
    private static final String CAS_URL_LOGIN = "cas.url.login"; 
    private static final String CAS_URL_LOGOUT = "cas.url.logout"; 
    private static final String CAS_URL_SERVICE = "cas.url.service"; 
    private static final String CAS_URL_CALLBACK = "cas.url.callback"; 
    private static final String CAS_REDIRECT_TARGET = "cas.redirect.target"; 

    @Inject 
    private Environment env; 

    @Inject 
    private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler; 

    @Inject 
    private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler; 

    @Inject 
    @Qualifier("casUserDetailsService") 
    private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casAuthenticationUserDetailsService; 

    @Inject 
    @Qualifier("formUserDetailsService") 
    private UserDetailsService userDetailsService; 

    @Inject 
    private Http401UnauthorizedEntryPoint authenticationEntryPoint; 

    @Bean 
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { 
     return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_SERVER)); 
    } 

    @Bean(name="webAuthProvider") 
    public CasAuthenticationProvider webCasAuthenticationProvider() { 
     CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); 
     casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); 
     casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache()); 
     casAuthenticationProvider.setKey("CAS_WEB_AUTHENTICATION_PROVIDER"); 
     casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService); 
     casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource()); 
     casAuthenticationProvider.setServiceProperties(webServiceProperties()); 

     return casAuthenticationProvider; 
    }//CasAuthenticationProvider 

    @Bean(name="mobileAuthProvider") 
    public CasAuthenticationProvider mobileCasAuthenticationProvider() { 
     CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); 
     casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); 
     casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache()); 
     casAuthenticationProvider.setKey("CAS_MOBILE_AUTHENTICATION_PROVIDER"); 
     casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService); 
     casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource()); 
     casAuthenticationProvider.setServiceProperties(mobileServiceProperties()); 
     return casAuthenticationProvider; 
    }//CasAuthenticationProvider 

    @Bean 
    public SingleSignOutFilter singleSignOutFilter() { 
     SingleSignOutFilter filter = new SingleSignOutFilter(); 
     return filter; 
    }//SingleSignOutFilter 

    @Inject 
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
     auth 
      .userDetailsService(userDetailsService) 
      .passwordEncoder(passwordEncoder()) 
      .and() 
      .authenticationProvider(mobileCasAuthenticationProvider()) 
      .authenticationProvider(webCasAuthenticationProvider()); 
    } 

    @Bean(name = "webCasFilter") 
    public CasAuthenticationFilter webCasAuthenticationFilter() throws Exception { 
     CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter(); 
     authenticationFilter.setBeanName("webCasFilter"); 
     authenticationFilter.setAuthenticationManager(authenticationManager()); 
     authenticationFilter.setServiceProperties(webServiceProperties()); 
     authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas")); 
     //authenticationFilter.setFilterProcessesUrl("/auth/cas"); 
     SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler = 
      new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET)); 
     authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler); 
     return authenticationFilter; 
    }//CasAuthenticationFilter 


    @Bean(name = "mobileCasFilter") 
    public CasAuthenticationFilter mobileCasAuthenticationFilter() throws Exception { 
     CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter(); 
     authenticationFilter.setBeanName("mobileCasFilter"); 
     authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas/mobile")); 
     authenticationFilter.setAuthenticationManager(authenticationManager()); 
     authenticationFilter.setServiceProperties(mobileServiceProperties()); 
     //authenticationFilter.setFilterProcessesUrl("/auth/cas/mobile"); 
     SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler = 
      new SimpleUrlAuthenticationSuccessHandler("/mobile/deep-link"); 
     authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler); 
     return authenticationFilter; 
    }//CasAuthenticationFilter 

    @Bean(name="webCasAuthenticationEntryPoint") 
    public CasAuthenticationEntryPoint webCasAuthenticationEntryPoint() { 
     CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); 
     entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN)); 
     entryPoint.setServiceProperties(webServiceProperties()); 
     return entryPoint; 
    }//CasAuthenticationEntryPoint 

    @Bean(name="mobileCasAuthenticationEntryPoint") 
    public CasAuthenticationEntryPoint mobileCasAuthenticationEntryPoint() { 
     CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); 
     entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN)); 
     entryPoint.setServiceProperties(mobileServiceProperties()); 
     return entryPoint; 
    }//CasAuthenticationEntryPoint 

    @Override 
    public void configure(WebSecurity web) throws Exception { 
     web 
      .ignoring() 
      .antMatchers("/scripts/**/*.{js,html}") 
      .antMatchers("/bower_components/**") 
      .antMatchers("/i18n/**") 
      .antMatchers("/assets/**") 
      .antMatchers("/swagger-ui/**") 
      .antMatchers("/test/**") 
      .antMatchers("/console/**"); 
    } 

    @Bean 
    public PasswordEncoder passwordEncoder() { 
     return new BCryptPasswordEncoder(); 
    } 

    private class CasRedirectionFilter implements Filter { 

     public void init(FilterConfig fConfig) throws ServletException { 
     } 

     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { 
      HttpServletResponse res = (HttpServletResponse) response; 
      //CasAuthenticationEntryPoint caep = casAuthenticationEntryPoint(); 
      res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 
      HttpServletRequest req = (HttpServletRequest) request;; 
      String contextPath = req.getRequestURI(); 
      if(contextPath.equals("/api/login/mobile")){ 
       String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas/mobile"; 
       res.setHeader("Location", redirectUrl); 
      }else { 
       String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas";     
       res.setHeader("Location", redirectUrl); 
      } 
     } 

     public void destroy() { 
     } 
    } 

    @Bean 
    public FilterChainProxy loginFilter() throws Exception { 
     List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>(); 
     chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/cas"), new CasRedirectionFilter())); 
     chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/mobile"), new CasRedirectionFilter())); 
     log.debug("loginFilter {}", chains); 
     return new FilterChainProxy(chains); 
    } 

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http 
      .sessionManagement() 
      .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) 
      .and() 
      .csrf() 
      .disable() 
      .addFilterBefore(mobileCasAuthenticationFilter(),CasAuthenticationFilter.class) 
      .addFilterBefore(webCasAuthenticationFilter(),CasAuthenticationFilter.class) 
      .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class) 
      .addFilter(loginFilter()) 
      .exceptionHandling() 
      .authenticationEntryPoint(authenticationEntryPoint) 
      .and() 
      .logout() 
      .logoutUrl("/api/logout") 
      .clearAuthentication(true) 
      .invalidateHttpSession(true) 
      .deleteCookies("JSESSIONID") 
      .logoutSuccessUrl(env.getRequiredProperty(CAS_URL_LOGOUT)) 
      .permitAll() 
      .and() 
      .headers() 
      .frameOptions() 
      .disable() 
      .and() 
      .formLogin() 
      //.defaultSuccessUrl(env.getRequiredProperty(CAS_REDIRECT_TARGET), true) 
      .successHandler(ajaxAuthenticationSuccessHandler) 
      .failureHandler(ajaxAuthenticationFailureHandler) 
      .loginProcessingUrl("/api/authentication") 
      .usernameParameter("j_username") 
      .passwordParameter("j_password") 
      .permitAll() 
      .and() 
      .authorizeRequests() 
      .antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/api/**").permitAll() 
      .antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll() 
      .antMatchers("/app/**").authenticated() 
      .antMatchers(HttpMethod.GET, "/api/login").authenticated() 
      .antMatchers("/api/login/mobile").authenticated() 
      .antMatchers("/api/login/cas").authenticated() 
      .antMatchers("/api/register").permitAll() 
      .antMatchers("/api/activate").permitAll() 
      .antMatchers("/api/authenticate").authenticated() 
      .antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/api/**").authenticated() 
      .antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/mobile/**").permitAll() 
      .antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN) 
      .antMatchers("/protected/**").authenticated(); 

    } 

    @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) 
    private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { 
     @Inject 
     ConferenceRepository conferenceRepository; 
     @Inject 
     UserRepository userRepository; 

     public GlobalSecurityConfiguration() { 
      super(); 
     } 

     @Override 
     protected MethodSecurityExpressionHandler createExpressionHandler() { 
      PermissionChecker permissionEvaluator = new PermissionChecker(conferenceRepository, userRepository); 

      DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); 
      expressionHandler.setPermissionEvaluator(permissionEvaluator); 
      return expressionHandler; 
     } 
    } 

    @Bean(name="webServiceProperties") 
    public ServiceProperties webServiceProperties() { 
     ServiceProperties serviceProperties = new ServiceProperties(); 
     serviceProperties.setService("http://localhost:8080/auth/cas"); 
     serviceProperties.setSendRenew(true); 
     serviceProperties.setAuthenticateAllArtifacts(true); 
     return serviceProperties; 
    }//serviceProperties 

    @Bean(name="mobileServiceProperties") 
    public ServiceProperties mobileServiceProperties() { 
     ServiceProperties serviceProperties = new ServiceProperties(); 
     serviceProperties.setService("http://localhost:8080/auth/cas/mobile"); 
     serviceProperties.setSendRenew(true); 
     serviceProperties.setAuthenticateAllArtifacts(true); 
     return serviceProperties; 
    }//serviceProperties 
} 

これはある程度機能します。モバイル認証フローは、それがとして働いて発行されると意図したものではなく、フロントエンドの問題/api/login/cas CASからTicketGrantingTicketは最初service=/auth/cas/mobileに対してモバイルフィルターを使用してチェックされているが、これは当然のチケットを無効casWebAuthenticationFilter用途を使用してTGT、その後の検証を無効にしているservice=/auth/casのために発行されたとき。

これで、CasAuthenticationFilterに特定のチケットのみを処理させる方法を考えていませんか?おそらく、私の思想の中で、よりシンプルな解決策が見えないほど絡み合っているのでしょうか?たぶん私は別の2つのHTTPセキュリティの設定を行う必要がありますか?

EDIT: それはすべて私がAuthenticationProviderを入れた順番に沸くようだ:

@Inject 
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
     auth 
      .userDetailsService(userDetailsService) 
      .passwordEncoder(passwordEncoder()) 
      .and() 
      .authenticationProvider(webCasAuthenticationProvider()) 
      .authenticationProvider(mobileCasAuthenticationProvider()); 
    } 

mobileAuthenticationProider()は、まず、私は順序を切り替えるとき1にはないモバイルログイン作品やウェブを行くときそれらが呼び出されるとモバイル認証が失敗し、Web認証が失敗します。

答えて

0

私はそれを稼働させました。それは最高の、最も堅牢なソリューションのようには見えません。それにもかかわらず、ここでそれが行く:

@Bean 
    public AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() { 

     return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() { 

      @Override 
      public WebAuthenticationDetails buildDetails(
       HttpServletRequest request) { 
       return new CustomAuthenticationDetails(request); 
      } 

     }; 
    } 

    @Bean 
    public AuthenticationProvider customAuthenticationProvider() { 
     return new AuthenticationProvider() { 
      @Override 
      public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
       String serviceUrl; 
       serviceUrl = ((CustomAuthenticationDetails) authentication.getDetails()).getURI(); 
       if (serviceUrl.equals(env.getRequiredProperty(CAS_URL_CALLBACK_MOBILE))) { 
        return mobileCasAuthenticationProvider().authenticate(authentication); 
       } else { 
        return webCasAuthenticationProvider().authenticate(authentication); 
       } 

      } 

      public boolean supports(final Class<?> authentication) { 
       return (UsernamePasswordAuthenticationToken.class 
        .isAssignableFrom(authentication)) 
        || (CasAuthenticationToken.class.isAssignableFrom(authentication)) 
        || (CasAssertionAuthenticationToken.class 
        .isAssignableFrom(authentication)); 
      } 
     }; 
    } 

私は、私が本当に必要な2つを区別し、私のカスタムAuthenticationProviderを追加しました。別のカスタムクラス、つまりCustomAuthenticationDetailsを使用して、要求がどこから来たのかに関する情報を格納します。

public class CustomAuthenticationDetails extends WebAuthenticationDetails { 
    private final Logger log = LoggerFactory.getLogger(CustomAuthenticationDetails.class); 
    private final String URI; 
    private final String sessionId; 
    public CustomAuthenticationDetails(HttpServletRequest request) { 
     super(request); 
     this.URI = request.getRequestURI(); 
     HttpSession session = request.getSession(false); 
     this.sessionId = (session != null) ? session.getId() : null; 
    } 

    public String getURI() { 
     return URI; 
    } 

    public String getSessionId() { 
     return sessionId; 
    } 
} 

そして、すべてこれはauthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource());を使用してAuthenticationFilterで一緒に配線されています。将来の問題で誰かを助けることができるか、少なくとも正しい方向でリードすることを願っています。

関連する問題