我在Srping Cloud gateway整合security+webFlux做用户名密码登录校验时,总是返回"Invalid Credentials",调试了下代码,用户名和密码验证是通过的,真的很奇怪,怀疑是不是webFlux框架的问题,下面我贴出部分代码:

全局config类如下

package cn.com.xxx.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
 * @project: xxx
 * @description: security主配置类。
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-07-06 guopengfei@xxx.com.cn Create 1.0
@EnableWebFluxSecurity
public class SecurityConfig
    @Autowired
    private AuthenticationSuccessHandler                  authenticationSuccessHandler;
    @Autowired
    private AuthenticationFaillHandler                    authenticationFaillHandler;
    @Autowired
    private CustomHttpBasicServerAuthenticationEntryPoint customHttpBasicServerAuthenticationEntryPoint;
    // security的鉴权排除列表
    private static final String[] excludedAuthPages = { "/auth/login", "/auth/logout", "/health", "/api/socket/**" };
     * 将登陆后的用户及权限信息存入session中
     * @return
    @Bean
    ServerSecurityContextRepository serverSecurityContextRepository()
        return new WebSessionServerSecurityContextRepository();
    @Bean
    SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception
        http.authorizeExchange().pathMatchers(excludedAuthPages).permitAll() // 无需进行权限过滤的请求路径
            .pathMatchers(HttpMethod.OPTIONS).permitAll() // option 请求默认放行
            .anyExchange().authenticated().and().httpBasic().and().formLogin().loginPage(
                "/auth/login")
            .authenticationSuccessHandler(authenticationSuccessHandler) // 认证成功
            .authenticationFailureHandler(authenticationFaillHandler) // 登陆验证失败
            .and().exceptionHandling().authenticationEntryPoint(customHttpBasicServerAuthenticationEntryPoint) // 基于http的接口请求鉴权失败
            .and().csrf().disable()// 必须支持跨域
            .logout().logoutUrl("/auth/logout");
        // .logoutSuccessHandler(logoutSuccessHandlerWebFlux);//成功登出时调用的自定义处理类
        return http.build();
     * 如果使用本方法实现,需要在下面的方法体中全量缓存用户信息到session中,
     * 而如果使用SecurityUserDetailsService,则可以自定义从数据库中读取用户信息,而不是从 缓存中
     * @return
    // @Bean
    // public MapReactiveUserDetailsService userDetailsService()
    // User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
    // UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
    // UserDetails admin = userBuilder.username("admin").password("admin").roles("USER",
    // "ADMIN").build();
    // return new MapReactiveUserDetailsService(rob, admin);
     * 密码加密工具
     * @return
    @Bean
    public PasswordEncoder passwordEncoder()
        return new BCryptPasswordEncoder();

 校验成功后的处理类:

package cn.com.xxx.config.security;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.WebFilterChainServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.com.xxx.commons.enums.BobfintechErrorNoEnum;
import cn.com.xxx.commons.pojo.BaseResponse;
import cn.com.xxx.commons.pojo.ResponseData;
import cn.com.xxx.pojo.AuthUserDetails;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
 * @project: xxx
 * @description: 登陆成功后执行的处理器,认证成功后返回给客户端的信息
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-07-06 guopengfei@xxx.com.cn Create 1.0
 * @copyright ©2019-2020 xxx。
@Component
@Slf4j
public class AuthenticationSuccessHandler extends WebFilterChainServerAuthenticationSuccessHandler
    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication)
        ServerWebExchange exchange = webFilterExchange.getExchange();
        ServerHttpResponse response = exchange.getResponse();
        // 设置headers
        HttpHeaders httpHeaders = response.getHeaders();
        httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
        httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
        // 设置body TODO
        Map<String, Object> resultMap = new HashMap<String, Object>();
        resultMap.put("resultCode", "fail");
        byte[] dataBytes = {};
        ObjectMapper mapper = new ObjectMapper();
        try {
            User user = (User) authentication.getPrincipal();
            AuthUserDetails userDetails = buildUser(user);
            byte[] authorization = (userDetails.getUsername() + ":" + userDetails.getPassword()).getBytes();
            String token = Base64.getEncoder().encodeToString(authorization);
            httpHeaders.add(HttpHeaders.AUTHORIZATION, token);
            resultMap.put("data", userDetails);
            dataBytes = mapper.writeValueAsBytes(resultMap);
        catch (Exception ex) {
            log.error("网关认证成功后返回给客户端信息时处理失败:[{}]", ex.getMessage(), ex);
            BaseResponse responseData = ResponseData
                .out(BobfintechErrorNoEnum.COM_BOBFINTECH_SERVICE_AUTHORIZE_ERROR);
            dataBytes = responseData.toString().getBytes();
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(dataBytes);
        return response.writeWith(Mono.just(bodyDataBuffer));
    private AuthUserDetails buildUser(User user)
        AuthUserDetails userDetails = new AuthUserDetails();
        userDetails.setUsername(user.getUsername());
        userDetails.setPassword(
            user.getPassword().substring(user.getPassword().lastIndexOf("}") + 1, user.getPassword().length()));
        return userDetails;

校验失败后的处理类

package cn.com.xxx.config.security;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
 * @Project: xxx
 * @Description: 登陆失败后执行的处理器,返回客户端错误信息
 * @Version 1.0.0
 * @Author
@Component
@Slf4j
public class AuthenticationFaillHandler implements ServerAuthenticationFailureHandler
    public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception)
        ServerWebExchange exchange = webFilterExchange.getExchange();
        ServerHttpResponse response = exchange.getResponse();
        // 设置headers
        HttpHeaders httpHeaders = response.getHeaders();
        httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
        httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
        // 设置body
        byte[] dataBytes = {};
        try {
            ObjectMapper mapper = new ObjectMapper();
            dataBytes = mapper.writeValueAsBytes(exception.getMessage().toString());
        catch (Exception ex) {
            ex.printStackTrace();
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(dataBytes);
        return response.writeWith(Mono.just(bodyDataBuffer));

自定义用户处理类

package cn.com.xxx.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import cn.com.bobfintech.commons.enums.BobfintechErrorNoEnum;
import cn.com.bobfintech.commons.pojo.BaseResponse;
import cn.com.bobfintech.commons.pojo.ResponseData;
import cn.com.bobfintech.commons.utils.StringUtils;
import cn.com.bobfintechgateway.dao.UserInfoDao;
import cn.com.bobfintechgateway.pojo.UserInfo;
import reactor.core.publisher.Mono;
 * @project: xxx
 * @description: 定义用户查找逻辑
 *               security 的认证和授权都离不开系统中的用户,实际用户都来自db,本例中采用的是系统配置的默认用户。
 *               UserDetailsRepositoryReactiveAuthenticationManager作为security的核心认证管理器,并调用userDetailsService去查找用户,本集成环境中自定义用户查找逻辑需实现ReactiveUserDetailsService接口并覆盖findByUsername(通过用户名查找用户)方法,核心代码如下
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-07-06 guopengfei@xxx.com.cn Create 1.0
 * @copyright ©2019-2020 xxx
@Component
public class SecurityUserDetailsService implements ReactiveUserDetailsService
    @Autowired
    private UserInfoDao userInfoDao;
    public Mono<UserDetails> findByUsername(String username)
        if (StringUtils.isBlank(username)) {
            BaseResponse baseResponse = ResponseData
                .out(BobfintechErrorNoEnum.COM_BOBFINTECH_SIGN_IN_USER_NAME_CAN_NOT_BE_NULL);
            return Mono.error(new UsernameNotFoundException(baseResponse.toString()));
        UserInfo userInfo = userInfoDao.findByUsername(username);
        if (userInfo == null) {
            return Mono.error(new UsernameNotFoundException("User Not Found"));
        // UserDetails user = User.withUsername(username).password(MD5Encoder.encode(userInfo
        // .getUserPassword(),
        // username))
        // .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("admin")).build();
        // return Mono.just(user);
        UserDetails user = User.withUsername(username)
            .password(
            userInfo
                    .getUserPassword())
            .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("admin")).build();
        return Mono.just(user);

pom配置文件:

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

以上就是我的主要代码,我使用Postman测试登录直接返回下方内容:

官方资料 https://docs.spring.io/spring-security/site/docs/5.3.2.RELEASE/reference/html5/#jc-webflux 对webFlux说的也不多,还请对这方面比较熟悉的大牛帮忙排查下问题,万分敢接

上面代码中类似下面的代码是我自己封装的返回信息类,大家可以忽略:

BaseResponse baseResponse = ResponseData
                .out(BobfintechErrorNoEnum.COM_BOBFINTECH_SIGN_IN_USER_NAME_CAN_NOT_BE_NULL);