我在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);