单点登录不在Spring Web应用程序中工作

时间:2014-12-17 10:55:08

标签: java spring authentication spring-security active-directory

我将我的Web应用程序配置为通过Active Directory repo进行身份验证,它工作正常,但我总是需要以登录表单插入凭据。

应用程序客户端将是在同一域中连接到公司网络的Windows计算机。

我需要配置我的网络应用程序,以自动验证在Windows中已经过身份验证的用户。

我正在使用此配置进行Spring Security:

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider
        user-service-ref="userDetailsService">
        <security:password-encoder hash="plaintext" />
    </security:authentication-provider> <!-- for DB authentication -->
    <security:authentication-provider
        ref="adAuthenticationProvider" />
</security:authentication-manager>

<bean id="adAuthenticationProvider"
    class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <constructor-arg name="domain" value="mydomain.it" />
    <constructor-arg name="url" value="ldap://domaincontroller.mydomain.it/" />
</bean>

注意我还需要辅助身份验证提供程序来提供数据库身份验证。

我还在IE(v 9)中设置了以下选项,该选项应启用自动登录:

enter image description here

但它不起作用......所以我的配置有什么问题?

注意#2 我正在使用Spring v 3.2.9和Spring Security v 3.2.3

3 个答案:

答案 0 :(得分:2)

&#39;权利&#39;方法是做Kerberos / SPNEGO。但是,服务器需要是Windows域上的受信任节点。如果您的服务器是Windows机器,那么这应该很容易。但是,如果它是一台NIX / Linux机器,那么它就可以成为真正的PITA。

这涉及诸如在Active Directory中使用SPN(服务主体名称)进行设置以及在服务器上安装一大堆东西以与A / D集成并对其进行身份验证的事情。

如果您(或您友好的Windows基础架构团队成员)对完成所有这些工作感到满意,那就去吧!但是,如果你不熟悉它,我应该警告你,当它不起作用时,诊断问题就好了。

但是有一个快速而肮脏的选项,它不会对网络上的服务器产生任何信任。事实上,整个事情可以包含在你的Spring应用程序中。这不是那么安全但非常有效的NTLM。它很容易设置。

首先,如果没有会话,您将需要一个Servlet过滤器来拦截请求并执行握手:

import java.io.IOException;

import org.apache.commons.codec.binary.Base64;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

/**
 * Simple authentication filter designed to get hold of the username via NTLM SSO. 
 * See Spring documentation on pre-authentication filters to see how this can be used.
 * </p>
 * <p>
 * <a href="http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#preauth">http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#preauth</a>
 * </p>
 */
@Component("ntlmFilter")
public class NtlmFilter implements Filter {

    private static Logger log = LoggerFactory.getLogger(NtlmFilter.class);

    public static final String USERNAME_KEY = "SM_USER";

    public NtlmFilter() {
        log.info("Initialising the NTLM filter.");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // No initialisation tasks.
    }

    @Override
    public void destroy() {
        // No destruction tasks.
    }

    /**
     * 
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (isAuthenticated(request)) {
            log.debug("Session already authenticated. Proceeding down filter chain.");
            setRequestHeaders(request);
            proceed(req, res, chain);
        } else {
            log.debug("Session not yet authenticated. Attempting to login...");
            login(request, response, chain);
        }
    }

    private void proceed(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {
        try {
            chain.doFilter(req, res);
        } catch (IOException e) {
            log.error("IOException processing NtlmAuthFilter Servlet filter.", e);
            throw e;
        } catch (ServletException e) {
            log.error("ServletException processing NtlmAuthFilter Servlet filter.", e);
            throw e;
        }
    }

    /**
     * If the user name has been stored in the session, then the user has been
     * authenticated by the application.
     */
    private boolean isAuthenticated(HttpServletRequest req) {
        if (req.getSession().getAttribute(USERNAME_KEY) != null) {
            return true;
        } else {
            return false;
        }
    }

    public void login(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {

        String username = null;

        String auth = req.getHeader("Authorization");
        if (auth == null) {
            // First phase. Return NTLM challenge headers.
            res.setHeader("WWW-Authenticate", "NTLM");
            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            res.setContentLength(0);
            res.flushBuffer();
            return;
        } else if (auth.startsWith("NTLM ")) {
            byte[] msg = Base64.decodeBase64(auth.substring(5));
            int off = 0, length, offset;
            if (msg[8] == 1) {
                // Login details are not valid. Reject.
                byte z = 0;
                byte[] msg1 = { (byte) 'N', (byte) 'T', (byte) 'L',
                        (byte) 'M', (byte) 'S', (byte) 'S', (byte) 'P',
                        z, (byte) 2, z, z, z, z, z, z, z, (byte) 40, z,
                        z, z, (byte) 1, (byte) 130, z, z, z, (byte) 2,
                        (byte) 2, (byte) 2, z, z, z, z, z, z, z, z, z,
                        z, z, z };
                res.setHeader(
                        "WWW-Authenticate", 
                        "NTLM " + Base64.encodeBase64String(msg1));
                res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                res.setContentLength(0);
                res.flushBuffer();
                return;
            } else if (msg[8] == 3) {
                // Login details seem valid. Grab the username.
                off = 30;

                length = msg[off + 9] * 256 + msg[off + 8];
                offset = msg[off + 11] * 256 + msg[off + 10];
                username = new String(msg, offset, length);
                username = canonicalUsername(username);
            }
        }

        req.getSession().setAttribute(USERNAME_KEY, username);

        setRequestHeaders(req);

        log.info("User details now stored in session: " + username);

        proceed(req, res, chain);
    }

    private void setRequestHeaders(HttpServletRequest req) {
        req.setAttribute(USERNAME_KEY, req.getSession().getAttribute(USERNAME_KEY));
    }

    /**
     * To avoid issues with comparing user names with differing case and spaces, 
     * this method strips out extraneous spaces and lower-cases it.
     */
    private String canonicalUsername(String username) {
        return username.replaceAll("[^a-zA-Z0-9#]", "").toLowerCase().trim();
    }

}

您可能会注意到这会在请求中创建SM_USER标头。如果确保此过滤器在RequestHeaderAuthenticationFilter之前运行,那么您有一个很好的设置,其中标头由SSO过滤器定义,然后所有内容都传递给标准的Spring身份验证处理。这可以这样做......

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
@Profile("secure")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired(required = true)
    @Qualifier("ntlmFilter")
    private Filter ntlmFilter;

    @Autowired(required = true)
    @Qualifier("headerAuthFilter")
    private Filter headerAuthFilter;

    // ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(ntlmFilter, RequestHeaderAuthenticationFilter.class)
            .anonymous().disable()
            .csrf().disable()
            .exceptionHandling().authenticationEntryPoint(http403ForbiddenEntryPoint());
    }

    @Bean(name = "headerAuthFilter")
    public Filter headerAuthFilter(AuthenticationManager authenticationManager) {
        RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
        filter.setPrincipalRequestHeader("SM_USER");
        filter.setAuthenticationManager(authenticationManager);
        filter.setExceptionIfHeaderMissing(false);
        return filter;
    }

    // ...

}

答案 1 :(得分:0)

如果要进行自动身份验证,则必须使用Kerberos / SPNEGO进行LDAP身份验证。

Spring有一个用于Kerberos / SPNEGO身份验证的模块检查此blog,它解释了Kerberos / SPNEGO如何工作以及如何配置spring安全性。

此外,您必须在IE中启用“Windows Integerated Authentication”,如下所示。

enter image description here

答案 2 :(得分:0)