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

TokenAuthentication

  • 方便授权时传递 token 并放入缓存
  • @Data
    @EqualsAndHashCode(callSuper = true)
    public class TokenAuthentication extends UsernamePasswordAuthenticationToken {
        private final String token;
        public TokenAuthentication(String token, Object principal, Object credentials) {
            super(principal, credentials);
            this.token = token;
        public TokenAuthentication(String token, Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
            super(principal, credentials, authorities);
            this.token = token;
    

    Config Class

    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    public class SecurityConfig {
        //可换为redis存储
        private final Map<String, SecurityContext> tokenCache = new ConcurrentHashMap<>();
        private static final String BEARER = "Bearer ";
        private static final String[] AUTH_WHITELIST = new String[]{"/login", "/actuator/**"};
        @Bean
        ReactiveAuthenticationManager reactiveAuthenticationManager() {
            final ReactiveUserDetailsService detailsService = userDetailsService();
            LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
            managers.add(authentication -> {
                //其他登陆方式(比如手机号验证码登陆)可在此设置不得抛出异常或者Mono.error
                return Mono.empty();
            //必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此AuthenticationManager会调用Mono.error造成后面的AuthenticationManager不生效
            managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(detailsService));
            return new DelegatingReactiveAuthenticationManager(managers);
        @Bean
        ServerSecurityContextRepository serverSecurityContextRepository() {
            return new ServerSecurityContextRepository() {
                @Override
                public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
                    if (context.getAuthentication() instanceof TokenAuthentication) {
                        TokenAuthentication authentication = (TokenAuthentication) context.getAuthentication();
                        tokenCache.put(authentication.getToken(), context);
                    return Mono.empty();
                @Override
                public Mono<SecurityContext> load(ServerWebExchange exchange) {
                    ServerHttpRequest request = exchange.getRequest();
                    String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
                    if (StringUtils.isEmpty(authorization) || !tokenCache.containsKey(authorization)) {
                        return Mono.empty();
                    return Mono.just(tokenCache.get(authorization));
        @Bean
        SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
            return http
                    .csrf().disable()
                    .formLogin().disable()
                    .httpBasic().disable()
                    .addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                    .authorizeExchange()
                    .pathMatchers(AUTH_WHITELIST).permitAll()
                    .anyExchange().authenticated()
                    .and().build();
        //修改为访问数据库的UserDetailsService即可
        @Bean
        ReactiveUserDetailsService 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);
        @Bean
        ServerAuthenticationConverter serverAuthenticationConverter() {
            final AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
            return exchange -> {
                String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
                if (StringUtils.isEmpty(token)) {
                    return Mono.just(anonymous);
                if (!token.startsWith(BEARER) || token.length() <= BEARER.length() || !tokenCache.containsKey(token.substring(BEARER.length()))) {
                    return Mono.just(anonymous);
                return Mono.just(tokenCache.get(token.substring(BEARER.length())).getAuthentication());
        @Bean
        AuthenticationWebFilter authenticationWebFilter() {
            AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(reactiveAuthenticationManager());
            NegatedServerWebExchangeMatcher negateWhiteList = new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(AUTH_WHITELIST));
            authenticationWebFilter.setRequiresAuthenticationMatcher(negateWhiteList);
            authenticationWebFilter.setServerAuthenticationConverter(serverAuthenticationConverter());
            authenticationWebFilter.setSecurityContextRepository(serverSecurityContextRepository());
            authenticationWebFilter.setAuthenticationFailureHandler((webFilterExchange, exception) -> Mono.error(new BadCredentialsException("权限不足")));
            return authenticationWebFilter;
    

    login api

    @RestController
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class LoginController {
        private final ReactiveAuthenticationManager authenticationManager;
        private final ServerSecurityContextRepository contextRepository;
        @PostMapping("/login")
        public Mono login(@RequestBody Login login, ServerWebExchange exchange) {
            final String token = UUID.randomUUID().toString().replaceAll("-", "");
            TokenAuthentication authenticationToken = new TokenAuthentication(token, login.getUsername(), login.getPassword());
            return authenticationManager.authenticate(authenticationToken).doOnSuccess(auth -> contextRepository.save(exchange, new SecurityContextImpl(authenticationToken))).then(Mono.just(token));
            

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。