spring-boot-starter-security + cxf-spring-boot-starter-jaxws 不起作用

问题描述

我正在尝试使用基于 CXF 的soap 服务安全地安装spring boot 应用程序。 我已经浏览了一千个论坛,但无法让它发挥作用。

我做了不同的测试,我在下面评论

  1. 如果我仅在具有基本身份验证的 cxf-rt-ws-security 级别启用安全性,则该应用程序可以正常工作,并且例如从 SOAPUI 调用时,它会验证基本身份验证 http 凭据。这里我为 cxf BasicAuthAuthorizationInterceptor 定义了一个拦截

     <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
         <version>3.4.0</version>
     </dependency>
    
     <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-ws-security</artifactId>
         <version>3.4.0</version>
     </dependency>
    

package com.byl.arquetipo.config;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cxf.binding.soap.interceptor.soapHeaderInterceptor;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {

    /** La constante logger. */
    private static final Logger logger = LoggerFactory.getLogger(BasicAuthAuthorizationInterceptor.class);

    /** La variable users. */
    Map<String,String> users = new HashMap<String,String>();

    /**
     * Obtiene users.
     *
     * @return users
     */
    public Map<String,String> getUsers() {
        return users;
    }

    /**
     * Establece users.
     *
     * @param users
     *            users
     */
    public void setUsers(Map<String,String> users) {
        this.users = users;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void handleMessage(Message message) throws Fault {

        AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);

        // If the policy is not set,the user did not specify credentials.
        // 401 is sent to the client to indicate that authentication is required.
        if (policy == null) {
            sendErrorResponse(message,HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }

        String username = policy.getUserName();
        String password = policy.getpassword();

        // CHECK USERNAME AND PASSWORD
        if (!checkLogin(username,password)) {
            logger.error("handleMessage: Invalid username or password for user: " + policy.getUserName());
            sendErrorResponse(message,HttpURLConnection.HTTP_FORBIDDEN);
        }
    }

    /**
     * Check login.
     *
     * @param username
     *            username
     * @param password
     *            password
     * @return <code>true</code>,si termina correctamente
     */
    private boolean checkLogin(String username,String password) {
        for (Map.Entry<String,String> item : users.entrySet()) {
            if (item.getKey().equals(username) && password.equals(item.getValue())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Send error response.
     *
     * @param message
     *            message
     * @param responseCode
     *            response code
     */
    private void sendErrorResponse(Message message,int responseCode) {
        Message outMessage = getoutMessage(message);
        outMessage.put(Message.RESPONSE_CODE,responseCode);

        // Set the response headers
        @SuppressWarnings("unchecked")
        Map<String,List<String>> responseHeaders = (Map<String,List<String>>) message.get(Message.PROTOCOL_HEADERS);

        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate",Arrays.asList(new String[] { "Basic realm=realm" }));
            responseHeaders.put("Content-Length",Arrays.asList(new String[] { "0" }));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            e.printstacktrace();
        }
    }

    /**
     * Obtiene out message.
     *
     * @param inMessage
     *            in message
     * @return out message
     */
    private Message getoutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getoutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setoutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }

    /**
     * Obtiene conduit.
     *
     * @param inMessage
     *            in message
     * @return conduit
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private Conduit getConduit(Message inMessage) throws IOException {
        Exchange exchange = inMessage.getExchange();
        EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
        //        Conduit conduit = exchange.getDestination().getBackChannel(inMessage,null,target);
        Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
        exchange.setConduit(conduit);
        return conduit;
    }

    /**
     * Close.
     *
     * @param outMessage
     *            out message
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void close(Message outMessage) throws IOException {
        OutputStream os = outMessage.getContent(OutputStream.class);
        os.flush();
        os.close();
    }

}

  1. 如果我此时启用 spring-boot-starter-security,它总是给我一个 401,我无法让它工作。同样通过启用 spring-boot-starter-security 我无法调试它进入 BasicAuthAuthorizationInterceptor 拦截

'

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

我必须为应用程序的其余部分启用 spring-boot-starter-security,因为我还为 spring 公开了 REST 服务。

有人可以指导我如何解决问题吗?

解决方法

如果您添加 Spring Security,您可能希望利用其处理基本身份验证的功能(并且不再使用您的拦截器)。

所以添加如下内容:

@EnableWebSecurity
public class BasicSecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests
                    .antMatchers("/services/*").hasRole("MEMBER")
                    .antMatchers("/").permitAll()
    )
        .httpBasic().realmName("Your realmname")
        .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
    ...
}

有关详细信息,请参阅 https://docs.spring.io/spring-boot/docs/2.4.5/reference/htmlsingle/#boot-features-security