Spring Security integrate with SSO(Siteminder)

This is the sample to integrate SSO to Java web app with spring security, typical authentication process:


In this case, Web app sever is not proxyed, and recieve request directly, so the user's request arrive to Web app server firstly


Core Spring security config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:sec="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/security 
                        http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <sec:http pattern="/assets/**" security="none" />
    <sec:http pattern="/views/logout**" security="none" />

    <bean id="userDetailsService" class="com.wilson.security.CustomUserDetailsService" />

    <sec:http entry-point-ref="loginHandler" use-expressions="true" auto-config="false"
        request-matcher="regex">
        <sec:intercept-url pattern="/**" access="isAuthenticated()" />
        <sec:logout logout-url="/logout" invalidate-session="true" success-handler-ref="logoutHandler" />
        <sec:custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
    </sec:http>

    <bean id="loginHandler" class="com.wilson.security.SSOLoginHandler">
        <property name="loginFormUrl" value="${sso.proxy}" />
        <property name="authProcessingURL" value="http://${fqdn}:${port}${context}/authenticate" />
    </bean>

    <bean id="logoutHandler" class="com.wilson.security.SSOLogoutHandler">
        <property name="defaultTargetUrl" value="/views/logout.jsp" />
        <property name="alwaysUseDefaultTargetUrl" value="true" />
    </bean>

    <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <constructor-arg value="/views/logout-failure.jsp"></constructor-arg>
    </bean>

    <bean id="authenticationFilter" class="com.wilson.security.SSOAuthenticationFilter">
        <property name="filterProcessesUrl" value="/authenticate" />
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="AuthenticationFailureHandler" ref="authenticationFailureHandler" />
        <property name="publicKey" value="${sso.publickey}" />
    </bean>

    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider user-service-ref="userDetailsService" />
    </sec:authentication-manager>

</beans>



So, first, we need to add a custom LoginUrlAuthenticationEntryPoint to redirect request to SSO when user open home page without login
public class SSOLoginHandler extends LoginUrlAuthenticationEntryPoint
{

    private final Logger logger = LoggerFactory.getLogger(SSOLoginHandler.class);
    private String authProcessingURL;

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
        final AuthenticationException authenticationException) throws IOException, ServletException
    {
        logger.debug("Preparing redirectiion to SSO PROXY...");
//        new DefaultRedirectStrategy().sendRedirect(request, response, this.getLoginFormUrl() + "?ref="
//            + authProcessingURL);
        String SSO_LOGIN_URL= "https://ssoserver.com/sso.jsp";
        new DefaultRedirectStrategy().sendRedirect(request, response,  SSO_LOGIN_URL + "?ref="
            + authProcessingURL);
    }

    public String getAuthProcessingURL()
    {
        return authProcessingURL;
    }

    public void setAuthProcessingURL(final String authProcessingURL)
    {
        this.authProcessingURL = authProcessingURL;
    }



Simplete logout which do some logging items..
public class SSOLogoutHandler extends SimpleUrlLogoutSuccessHandler
{

    private final Logger logger = LoggerFactory.getLogger(SSOLogoutHandler.class);

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

        super.onLogoutSuccess(request, response, authentication);
        logger.debug("Performing an SSO logout at: {}", this.getDefaultTargetUrl());
    }
}



Custom UserDeatailsService to load role and Grant Authority to user
public class CustomUserDetailsService implements UserDetailsService
{
    public static final String DEFAULT_AUTH_PASSWORD = "password";
    
   

    @Override
    public UserDetails loadUserByUsername(String soeid) throws UsernameNotFoundException
    {
        List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
        grantedAuths.add(new SimpleGrantedAuthority(***Service.queryUserRoleFromDatabase(soeid).toString()));
        
        UserDetails user = new User(soeid, DEFAULT_AUTH_PASSWORD, true, true, true, true, grantedAuths);      
        
        return user;
    }

}


Custom authetication filter to processe the response form SSO server after logicn
public class SSOAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{

    public static final String DEFAULT_AUTH_PASSWORD = "password";
    private final Logger logger = LoggerFactory.getLogger(SSOAuthenticationFilter.class);
    private Cipher cipher;

    public SSOAuthenticationFilter()
    {
        super.setPostOnly(false); // allow a GET request from SSO PROXY
    }

    @Override
    public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException
    {
      
        String[] sid = decodeSID(request);
        String soeid = sid[0];

        // token is expired if currentTimeMillis is greater then TIMESTAMP
        if (System.currentTimeMillis() > Long.parseLong(sid[1]))
        {
            logger.error("Authentication rejected for: {}", soeid);
            throw new NonceExpiredException("Authentication token is expired");
        }
        // saving decoded SOEID in a REQUEST to reuse it by obtainUsername()
        request.setAttribute("SSO_USER_SOEID", soeid);

        return super.attemptAuthentication(request, response);
    }

    @Override
    protected String obtainPassword(final HttpServletRequest request)
    {
        return DEFAULT_AUTH_PASSWORD;
    }

    @Override
    protected String obtainUsername(final HttpServletRequest request)
    {
        //SM_USER is coming from SSO after login 
        return (String) request.getAttribute("SM_USER");
    }

    private String[] decodeSID(final HttpServletRequest request)
    {
       .............add SSO server decode strtegy
    }

}



You may say above sample was not my case, what happens we have SSO setup in the proxy server as below?



only difference is in the login entry filter, we redirect to the web app authentication filter("/authenticate") as it's pre-logged in
public class SSOLoginHandler extends LoginUrlAuthenticationEntryPoint
{

    private final Logger logger = LoggerFactory.getLogger(SSOLoginHandler.class);
    private String authProcessingURL;

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
        final AuthenticationException authenticationException) throws IOException, ServletException
    {
        logger.debug("Preparing redirectiion to SSO PROXY...");
        new DefaultRedirectStrategy().sendRedirect(request, response, "/authenticate");
    }
  
}

猜你喜欢

转载自caerun.iteye.com/blog/2312806