Infinite loop in custom Spring Security application

天涯浪子 提交于 2019-12-06 12:05:55

your coding approach is valid. However, I can provide you with a slightly different but working approach. Before I start to explain the solution, here is the code:

WebSecurityConfig.java

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().
    antMatchers("/restapi").hasRole("USER")
    .and().addFilterBefore(new SsoTokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class).httpBasic()
    .and().authorizeRequests().antMatchers("/**").permitAll().anyRequest().authenticated();
}

@Override
protected void configure(AuthenticationManagerBuilder auth)
        throws Exception {
    // The order is important! During runtime Spring Security tries to find Provider-Implementations that
    // match the UsernamePasswordAuthenticationToken (which will be created later..). We must make sure
    // that daoAuthenticationProvider matches first. Why? Hard to explain, I figured it out with the debugger.
    auth.authenticationProvider(daoAuthenticationProvider());
    auth.authenticationProvider(tokenAuthenticationProvider());

}

@Bean
public AuthenticationProvider tokenAuthenticationProvider() {
    return new SsoTokenAuthenticationProvider();
}

@Bean
public AuthenticationProvider daoAuthenticationProvider() {
    // DaoAuthenticationProvider requires a userDetailsService object to be attached.
    // So we build one. This replaces the AuthenticationConfiguration, which is commented out below

    // Build the userDetailsService
    User userThatMustMatch = new User("michael", "password", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    Collection<UserDetails> users = new ArrayList<>();
    users.add(userThatMustMatch);
    InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(users);  

    // Create the DaoAuthenticationProvider that will handle all HTTP BASIC AUTH requests
    DaoAuthenticationProvider daoAuthProvider = new DaoAuthenticationProvider();
    daoAuthProvider.setUserDetailsService(userDetailsService);
    return daoAuthProvider;
}

SsoTokenAuthenticationFilter.java

public class SsoTokenAuthenticationFilter extends GenericFilterBean {

public final String HEADER_SECURITY_COOKIE = "LdapToken"; 

private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource<HttpServletRequest,?> ssoTokenAuthenticationDetailsSource = new SsoTokenWebAuthenticationDetailsSource();

public SsoTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    // check if SSO token is available. If not, pass down to next filter in chain
    try {
        Cookie[] cookies = httpRequest.getCookies();
        if (cookies == null){
            chain.doFilter(request, response);
            return;
        }
        Cookie ssoCookie = null;
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals("ssoToken"))
                ssoCookie = cookies[i];
            }
        if (ssoCookie == null){
            chain.doFilter(request, response);
            return;
        }

        // SSO token found, now authenticate and afterwards pass down to next filter in chain
        authenticateWithSsoToken(httpRequest);
        logger.debug("now the AuthenticationFilter passes down to next filter in chain");
        chain.doFilter(request, response);
    } catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
        SecurityContextHolder.clearContext();
        logger.error("Internal authentication service exception", internalAuthenticationServiceException);
        httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } catch (AuthenticationException authenticationException) {
        SecurityContextHolder.clearContext();
        logger.debug("No or invalid SSO token");
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    } 
}

private void authenticateWithSsoToken(HttpServletRequest request) throws IOException {
    System.out.println("+++ authenticateWithSSOToken +++");
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    authRequest.setDetails(ssoTokenAuthenticationDetailsSource.buildDetails(request));        

    // Delegate authentication to SsoTokenAuthenticationProvider, he will call the SsoTokenAuthenticationProvider <-- because of the configuration in WebSecurityConfig.java
    Authentication authResult = authenticationManager.authenticate(authRequest);
}}

SsoTokenAuthenticationProvider.java

public class SsoTokenAuthenticationProvider implements AuthenticationProvider {

public SsoTokenAuthenticationProvider() {

}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    SsoTokenWebAuthenticationDetails ssoTokenWebAuthenticationDetails = null;
    WebAuthenticationDetails webWebAuthenticationDetails = (WebAuthenticationDetails)authentication.getDetails();

    if (! (webWebAuthenticationDetails instanceof SsoTokenWebAuthenticationDetails)){
        // ++++++++++++++++++++++++
        // BASIC authentication....
        // ++++++++++++++++++++++++
        UsernamePasswordAuthenticationToken emptyToken = new UsernamePasswordAuthenticationToken(null, null);
        emptyToken.setDetails(null);
        return emptyToken; //return null works, too.
    }

    // ++++++++++++++++++++++++
    // LDAP authentication....
    // ++++++++++++++++++++++++
    ssoTokenWebAuthenticationDetails = (SsoTokenWebAuthenticationDetails)webWebAuthenticationDetails;       
    Cookie ssoTokenCookie = ssoTokenWebAuthenticationDetails.getSsoTokenCookie();

    // check if SSO cookie is available
    if (ssoTokenCookie == null){ 
        return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.
    }
    String username = ssoTokenCookie.getValue();

    // Do your SSO token authentication here
    if (! username.equals("michael"))
        return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.

    // Create new Authentication object. Name and password can be null (but you can set the values of course).
    // Be careful with your role names!
    // In WebSecurityConfig the role "USER" is automatically prefixed with String "ROLE_", so it is "ROLE_USER" in the end.
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    authRequest.setDetails(ssoTokenWebAuthenticationDetails);

    // Don't let spring decide.. you already have made the right decisions. Tell spring you have an authenticated user.
    // vielleicht ist dieses obere Kommentar auch bullshit... ich lese das morgen noch mal nach...
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return authentication;
}

@Override
public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

SsoTokenWebAuthenticationDetailsSource.java

public class SsoTokenWebAuthenticationDetailsSource extends
    WebAuthenticationDetailsSource {

@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    return new SsoTokenWebAuthenticationDetails(context);
}

}

SsoTokenWebAuthenticationDetails.java

public class SsoTokenWebAuthenticationDetails extends WebAuthenticationDetails {
private static final long serialVersionUID = 1234567890L;

private Cookie ssoTokenCookie;

public SsoTokenWebAuthenticationDetails(HttpServletRequest request) {
    super(request);
    // Fetch cookie from request
    Cookie[] cookies = request.getCookies();

    Cookie ssoTokenCookie = null;
    for (int i = 0; i < cookies.length; i++) {
        if (cookies[i].getName().equals("SSOToken"))
            ssoTokenCookie= cookies[i];
        }
    this.setSsoTokenCookie(ssoTokenCookie);
}

public Cookie getSsoTokenCookie() {
    return ssoTokenCookie;
}

public void setSsoTokenCookie(Cookie ssoTokenCookie) {
    this.ssoTokenCookie = ssoTokenCookie;
}
}

I describe the solution in a view words:

  1. The Config class secures any /restapi controller with role ROLE_USER. The authentication can be done using httpBasic authentication, but before you can try basic auth. you must try to authenticate the user by a ssoTokenCookie (if available). Therefore, you set the SsoTokenAuthenticationFilter as filter before basic auth. is applied.
  2. Inside the filter, you check if a ssoTokenCookie is available in request.
    • If yes, you delegate the authentication to the standard spring AuthenticationManager. The AuthenticationManager knows your own SsoTokenAuthenticationProvider implementation and delegates the authentication to it. Here, it is important to have the cookie information available. This can be done by use of a customized WebAuthenticationDetails.
    • if no, you pass down the work to the next filter in chain. It's no surprise, the standard BasicAuthenticationFilter will be called. Because you told Spring to use the standard daoAuthenticationProvider in WebSecurityConfig.java, Spring can authenticate the user when the proper credentials will be entered in the basic auth. dialog
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!