10

我一直在努力让 waffle 使用 spring java 配置与 spring 4.2.5 一起工作。我想我也可以帮助处于同样情况的其他人。

在通过 waffles NTLM 协议进行验证后,我们使用自定义 preWaffle 和 postWaffle 过滤器来验证用户是否存在于我们的数据库中。

我们还有使用 EnableGlobalMethodSecurity 注释来授权用户操作的方法。

至少可以说,要让它在 Spring Java 配置中工作很麻烦。您可以在下面的答案中找到我们的解决方案。我希望它会有所帮助。

4

1 回答 1

12

SpringConfiguration.java

// ... imports 
@Configuration
@EnableWebMvc
@EnableScheduling
@PropertySources({
    @PropertySource("classpath:app.properties")
    // ... Properties sources
})
@EnableTransactionManagement
@ComponentScan(basePackages = "com.our.package")
public class SpringConfiguration extends WebMvcConfigurerAdapter {

 // Our Spring configuration ...

}

安全配置.java

// ... imports 
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

  // Authentication manager configuration
  @Autowired
  private WindowsAuthenticationProviderWrapper authProvider;

  @Autowired
  private AuthenticationManagerBuilder auth;

  @Override
  protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authProvider);
  }

  @Bean
  public AuthenticationManager authenticationManager() throws Exception {
    return auth.getObject();
  }

  // Waffle configuration
  @Bean
  public Filter customPreAuthSecurityFilter() {
    return new CustomPreAuthSecurityFilter();
  }

  @Bean
  public Filter customNegotiateSecurityFilter() {
    return new CustomNegotiateSecurityFilter();
  }

  @Bean
  public WindowsAuthProviderImpl waffleAuthProvider(){
    return new WindowsAuthProviderImpl();
  }

  @Bean(name="negotiateSecurityFilterProvider")
  @Autowired
  public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(){
    NegotiateSecurityFilterProvider bean = new NegotiateSecurityFilterProvider(waffleAuthProvider());
    List<String> protocols = new ArrayList<>();
    protocols.add("Negotiate");
    bean.setProtocols(protocols);
    return bean;
  }

  @Bean
  public BasicSecurityFilterProvider basicSecurityFilterProvider(){
    return new BasicSecurityFilterProvider(waffleAuthProvider());
  }

  @Bean(name="waffleSecurityFilterProviderCollection")
  @Autowired
  public waffle.servlet.spi.SecurityFilterProviderCollection negotiateSecurityFilterProviderCollection() {
    final List<SecurityFilterProvider> lsp = new ArrayList<>();
    lsp.add(negotiateSecurityFilterProvider());
    lsp.add(basicSecurityFilterProvider());
    return new waffle.servlet.spi.SecurityFilterProviderCollection(lsp.toArray(new SecurityFilterProvider[]{}));
  }

  @Bean(name="negotiateSecurityFilterEntryPoint")
  @Autowired
  public waffle.spring.NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint() {
    final waffle.spring.NegotiateSecurityFilterEntryPoint ep = new waffle.spring.NegotiateSecurityFilterEntryPoint();
    ep.setProvider(negotiateSecurityFilterProviderCollection());
    return ep;
  }

  @Bean(name="negotiateSecurityFilter")
  @Autowired
  public waffle.spring.NegotiateSecurityFilter waffleNegotiateSecurityFilter(){
    waffle.spring.NegotiateSecurityFilter bean = new waffle.spring.NegotiateSecurityFilter();
    bean.setRoleFormat("both");
    bean.setPrincipalFormat("fqn");
    bean.setAllowGuestLogin(false);
    bean.setProvider(negotiateSecurityFilterProviderCollection());
    return bean;
  }

  // Static Mappings
  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/assets/**");
  }

  // Security filter chain
  // The custom filters can be removed if you only use waffle 
  // but this is how we added them
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // A user needs to have the role user and has to be authenticated
    http.exceptionHandling()
      .authenticationEntryPoint(negotiateSecurityFilterEntryPoint()).and()
      .addFilterBefore(customPreAuthSecurityFilter(), BasicAuthenticationFilter.class)
      .addFilterAfter(waffleNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
      .addFilterAfter(customNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
      .authorizeRequests().anyRequest().fullyAuthenticated();
     }
  }

为了能够自动装配 waffle authProvider,我创建了以下包装类。

WindowsAuthenticationProviderWrapper.java

// ... imports 
// This class purpose is only to make the Windows authentication provider autowireable in spring.
@Component
public class WindowsAuthenticationProviderWrapper extends WindowsAuthenticationProvider{}

根据要求(由于安全风险,一些代码已被剥离)。

CustomPreAuthFilter.java

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * This filter removes the excess negoatiate header sent by IE. If the client
 * has already authenticated, strip the Authorization header from the request.
 */
public class CustomPreAuthSecurityFilter extends GenericFilterBean {
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    SecurityContext sec = SecurityContextHolder.getContext();
    HttpServletRequest req = (HttpServletRequest) servletRequest;

    if(sec != null && sec.getAuthentication() != null) {
      req = new CustomServletRequestWrapper(req);
    }

    try {
      filterChain.doFilter(req, servletResponse);
    } catch (RuntimeException e) {
      sendUnauthorized((HttpServletResponse) servletResponse);
    }
  }

  private void sendUnauthorized(HttpServletResponse response) throws IOException {
    logger.warn("error logging in user");
    SecurityContextHolder.clearContext();
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
  }
}

CustomNegotiateSecurityFilter.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import waffle.servlet.WindowsPrincipal;
import waffle.spring.WindowsAuthenticationToken;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/**
 * Handle post NTLM authentication against system database
 */
public class CustomNegotiateSecurityFilter extends GenericFilterBean {

  @Autowired
  private UserDAO userDAO;

  @Autowired
  Environment env;

  private static final Logger LOGGER = LoggerFactory.getLogger(CustomNegotiateSecurityFilter.class);

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;
    SecurityContext sec = SecurityContextHolder.getContext();
    Authentication authentication = sec.getAuthentication();

    // Continue filter chain if we are anonymously authenticated or if DB authentication has already happened.
    if (authentication != null && authentication.getClass() == WindowsAuthenticationToken.class) {

      // The user is Authenticated with NTLM but needs to be checked against the DB.
      User user;

      try {
        // fetch user from DB ...
      } catch (Exception e) {
        // The could not be found in the DB.
        sendUnauthorized(response);
        return;
      }

      // The user was found in the DB.
      WindowsPrincipal principal = (WindowsPrincipal)authentication.getPrincipal();
      final CustomAuthenticationToken token = new CustomAuthenticationToken(principal); // This class extends WindowsAuthenticationToken

      // add roles to token ...

      sec.setAuthentication(token);
    }

    chain.doFilter(request, response);
  }

  private void sendUnauthorized(HttpServletResponse response) throws IOException {
    logger.warn("Could not log in user");
    SecurityContextHolder.clearContext();
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
  }

  private void addRoleToAuthentication(WindowsAuthenticationToken authentication, String role) {
      for(GrantedAuthority authority : authentication.getAuthorities()) {
        if(authority.getAuthority().equals(role)) {
          return;
        }
      }
      authentication.getAuthorities().add(new SimpleGrantedAuthority(role));
  }
}

编辑

对于那些在这里询问的人来说,这是一种实现方式。CustomServletRequestWrapper:

class CustomServletRequestWrapper extends HttpServletRequestWrapper {


    public CustomServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    public String getHeader(String name) {
        if(name.equals("Authorization"))
            return null;
        String header = super.getHeader(name);
        return (header != null) ? header : super.getParameter(name); // Note: you can't use getParameterValues() here.
    }

    public Enumeration getHeaderNames() {
        List<String> names = Collections.list(super.getHeaderNames());

        names.addAll(Collections.list(super.getParameterNames()));
        names.remove("Authorization");
        return Collections.enumeration(names);
    }

}

如果您需要更多信息,请不要犹豫。

于 2016-04-28T08:27:36.407 回答