springboot2 webflux集成spring security做权限登陆校验

一、主要pom依赖包


    org.springframework.boot
    spring-boot-starter


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.boot
    spring-boot-starter-data-redis


    org.apache.commons
    commons-pool2
    2.5.0


    org.springframework.boot
    spring-boot-starter-webflux


    org.springframework.session
    spring-session-core



    org.springframework.session
    spring-session-data-redis

二、application root配置

@SpringBootApplication
//开启webflux
@EnableWebFlux
//使用redis存储session信息
@EnableRedisWebSession
public class AuthorityParentApplication {

	public static void main(String[] args) {
        Hooks.onOperatorDebug();
		SpringApplication.run(AuthorityParentApplication.class, args);
	}
}

三、权限校验配置

先把主要的配置类信息分别说一下,后面会有组合后的configuration类,类名为:SecurityConfig

1、过滤器配置

webflux security配置调用链信息的主类为:ServerHttpSecurity,里面有OAuth2,formlogin等登陆方式的默认配置

管理系统的话我们启用FormLoginSpec即刻,就是传统的cookie配session的登陆验证机制

/**
     * 此处的代码会放在SecurityConfig类中,此处只是摘要下,里面的handler在下文会详细介绍
     * @param http
     * @return
     */
@Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
        ServerHttpSecurity.FormLoginSpec formLoginSpec = http.formLogin();
        formLoginSpec.authenticationSuccessHandler(createAuthenticationSuccessHandler())
                .loginPage("/login")
                .authenticationFailureHandler(createAuthenticationFailureHandler());
        return formLoginSpec.and()
                .csrf().disable()
                .httpBasic().disable()
                .authorizeExchange()
                .pathMatchers(AUTH_WHITELIST).permitAll()
                .anyExchange().authenticated()
                .and().build();
    }

最后调用ServerHttpSecurity的build方法,从源码中可以查看构造的filter调用链,及配置信息等

public SecurityWebFilterChain build() {
		if (this.built != null) {
			throw new IllegalStateException("This has already been built with the following stacktrace. " + buildToString());
		}
		this.built = new RuntimeException("First Build Invocation").fillInStackTrace();
		if (this.headers != null) {
			this.headers.configure(this);
		}
		WebFilter securityContextRepositoryWebFilter = securityContextRepositoryWebFilter();
		if (securityContextRepositoryWebFilter != null) {
			this.webFilters.add(securityContextRepositoryWebFilter);
		}
		if (this.httpsRedirectSpec != null) {
			this.httpsRedirectSpec.configure(this);
		}
		if (this.csrf != null) {
			this.csrf.configure(this);
		}
		if (this.cors != null) {
			this.cors.configure(this);
		}
		if (this.httpBasic != null) {
			this.httpBasic.authenticationManager(this.authenticationManager);
			this.httpBasic.configure(this);
		}
		if (this.formLogin != null) {
			this.formLogin.authenticationManager(this.authenticationManager);
			if (this.securityContextRepository != null) {
				this.formLogin.securityContextRepository(this.securityContextRepository);
			}
			this.formLogin.configure(this);
		}
		if (this.oauth2Login != null) {
			this.oauth2Login.configure(this);
		}
		if (this.resourceServer != null) {
			this.resourceServer.configure(this);
		}
		if (this.client != null) {
			this.client.configure(this);
		}
		this.loginPage.configure(this);
		if (this.logout != null) {
			this.logout.configure(this);
		}
		this.requestCache.configure(this);
		this.addFilterAt(new SecurityContextServerWebExchangeWebFilter(), SecurityWebFiltersOrder.SECURITY_CONTEXT_SERVER_WEB_EXCHANGE);
		if (this.authorizeExchange != null) {
			ServerAuthenticationEntryPoint authenticationEntryPoint = getAuthenticationEntryPoint();
			ExceptionTranslationWebFilter exceptionTranslationWebFilter = new ExceptionTranslationWebFilter();
			if (authenticationEntryPoint != null) {
				exceptionTranslationWebFilter.setAuthenticationEntryPoint(
					authenticationEntryPoint);
			}
			ServerAccessDeniedHandler accessDeniedHandler = getAccessDeniedHandler();
			if (accessDeniedHandler != null) {
				exceptionTranslationWebFilter.setAccessDeniedHandler(
						accessDeniedHandler);
			}
			this.addFilterAt(exceptionTranslationWebFilter, SecurityWebFiltersOrder.EXCEPTION_TRANSLATION);
			this.authorizeExchange.configure(this);
		}
		AnnotationAwareOrderComparator.sort(this.webFilters);
		List sortedWebFilters = new ArrayList<>();
		this.webFilters.forEach( f -> {
			if (f instanceof OrderedWebFilter) {
				f = ((OrderedWebFilter) f).webFilter;
			}
			sortedWebFilters.add(f);
		});
        //sortedWebFilters中保存了登陆权限校验的所有filter信息
		sortedWebFilters.add(0, new ServerWebExchangeReactorContextWebFilter());
		return new MatcherSecurityWebFilterChain(getSecurityMatcher(), sortedWebFilters);
	}

2、自定义用户信息获取方式

获取用户信息的接口定义是:ReactiveUserDetailsService,官网的实现是:MapReactiveUserDetailsService,给出demo中是代码写死用户名密码、角色,并用此类存储。但实际中更多的从数据库中获取配置的用户角色信息,所以我们实现ReactiveUserDetailsService接口,自定义从mysql中获取用户信息的类,代码如下:

import com.sisheng.authority.common.enums.ErrorEnum;
import com.sisheng.authority.common.exception.BusinessException;
import com.sisheng.authority.repository.user.entity.SystemUser;
import com.sisheng.authority.service.permission.SystemPermissionService;
import com.sisheng.authority.service.rolepermission.SystemRolePermissionService;
import com.sisheng.authority.service.user.SystemUserService;
import com.sisheng.authority.service.userrole.SystemUserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import reactor.core.publisher.Mono;

import java.util.ArrayList;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.service.security.impl
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-04 13:27
 */
public class MysqlReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService {

    @Autowired
    private SystemUserService userService;
    @Autowired
    private SystemUserRoleService userRoleService;
    @Autowired
    private SystemRolePermissionService rolePermissionService;
    @Autowired
    private SystemPermissionService permissionService;

    @Override
    public Mono findByUsername(String username) {
        Mono systemUserMono = userService.findByCode(username);
        return systemUserMono
                .switchIfEmpty(Mono.error(() -> new BusinessException(ErrorEnum.USER_NOT_EXISTS)))
                .flatMap(systemUser -> userRoleService.findByUserId(systemUser.getId())
                        .collect(ArrayList::new, (roleIdList, userRole) -> roleIdList.add(userRole.getRoleId())))
                .flatMap(roleIdList -> rolePermissionService.findByRoleIdList(roleIdList)
                        .collect(ArrayList::new, (permissionIdList, rolePermission) -> permissionIdList.add(rolePermission.getPermissionId()))
                        .flatMap(permissionIdList -> permissionService.findByPermissionIdList(permissionIdList)
                                .collect(ArrayList::new, (authorityList, permission) -> authorityList.add(new SimpleGrantedAuthority(permission.getCode())))
                        )
                        .zipWith(systemUserMono, (authorityList, systemUser) -> new User(systemUser.getCode(), systemUser.getPassword(), authorityList)));
    }
}
import com.sisheng.authority.common.enums.ErrorEnum;
import com.sisheng.authority.common.exception.BusinessException;
import com.sisheng.authority.repository.user.entity.SystemUser;
import com.sisheng.authority.repository.user.mapper.SystemUserMapper;
import com.sisheng.authority.service.user.SystemUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.WeekendSqls;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.service.user.impl
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-04 13:49
 */
@Service
public class SystemUserServiceImpl implements SystemUserService {

    @Autowired
    private SystemUserMapper userMapper;
    @Autowired
    @Qualifier("mysqlScheduler")
    private Scheduler mysqlScheduler;

    @Override
    public Mono findByCode(String code) {
        Example example = Example.builder(SystemUser.class).andWhere(
                WeekendSqls.custom().andEqualTo(SystemUser::getCode, code)
        ).build();
        return Mono.defer(() -> Mono.justOrEmpty(userMapper.selectOneByExample(example)))
                //使用自定义的线程池执行会阻塞的任务,防止reactor线程被阻塞
                .subscribeOn(mysqlScheduler);
    }
}
import com.sisheng.authority.repository.userrole.entity.SystemUserRole;
import com.sisheng.authority.repository.userrole.mapper.SystemUserRoleMapper;
import com.sisheng.authority.service.userrole.SystemUserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.WeekendSqls;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.service.userrole.impl
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-04 15:29
 */
@Service
public class SystemUserRoleServiceImpl implements SystemUserRoleService {

    @Autowired
    private SystemUserRoleMapper userRoleMapper;
    @Autowired
    @Qualifier("mysqlScheduler")
    private Scheduler mysqlScheduler;

    @Override
    public Flux findByUserId(Long userId) {
        Example example = Example.builder(SystemUserRole.class).andWhere(
                WeekendSqls.custom().andEqualTo(SystemUserRole::getUserId, userId)
        ).build();
        return Flux.defer(() ->
                Flux.fromIterable(userRoleMapper.selectByExample(example))).subscribeOn(mysqlScheduler);
    }
import com.sisheng.authority.repository.rolepermission.entity.SystemRolePermission;
import com.sisheng.authority.repository.rolepermission.mapper.SystemRolePermissionMapper;
import com.sisheng.authority.service.rolepermission.SystemRolePermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.WeekendSqls;

import java.util.List;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.service.rolepermission.impl
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-04 16:27
 */
@Service
public class SystemRolePermissionServiceImpl implements SystemRolePermissionService {

    @Autowired
    private SystemRolePermissionMapper rolePermissionMapper;
    @Autowired
    @Qualifier("mysqlScheduler")
    private Scheduler mysqlScheduler;

    @Override
    public Flux findByRoleIdList(List roleIdList) {
        Example example = Example.builder(SystemRolePermission.class).andWhere(
                WeekendSqls.custom().andIn(SystemRolePermission::getRoleId, roleIdList)
        ).build();
        return Flux.defer(() -> Flux.fromIterable(rolePermissionMapper.selectByExample(example)))
                .subscribeOn(mysqlScheduler);
    }
}
import com.sisheng.authority.repository.permission.entity.SystemPermission;
import com.sisheng.authority.repository.permission.mapper.SystemPermissionMapper;
import com.sisheng.authority.service.permission.SystemPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.WeekendSqls;

import java.util.List;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.service.permission.impl
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-04 16:34
 */
@Service
public class SystemPermissionServiceImpl implements SystemPermissionService {

    @Autowired
    private SystemPermissionMapper permissionMapper;
    @Autowired
    @Qualifier("mysqlScheduler")
    private Scheduler mysqlScheduler;

    @Override
    public Flux findByPermissionIdList(List permissionIdList) {
        Example example = Example.builder(SystemPermission.class).andWhere(
                WeekendSqls.custom().andIn(SystemPermission::getId, permissionIdList)
        ).build();
        return Flux.defer(() -> Flux.fromIterable(permissionMapper.selectByExample(example)))
                .subscribeOn(mysqlScheduler);
    }
}

3、自定义登陆成功或失败后的handler

webflux security FormLoginSpec登陆方式默认的登陆成功handler为:RedirectServerAuthenticationSuccessHandler,登陆失败handler为:RedirectServerAuthenticationFailureHandler,这两个handler都是跳转链接,但现在大多数项目为前后端分离的项目,前端大部分是单页应用,路由又前端来控制,接口使用json传输数据,所以我们自定义success和failure的handler,当成功或失败时,返回json数据给前端。

3.1、默认登陆成功/失败的handler设置

从ServerHttpSecurity类中可以看到设置这两个handler的地方,FormLoginSpec为ServerHttpSecurity的内部类,部分源码如下:

public class FormLoginSpec {
        //默认的登陆成功后执行的handler
		private final RedirectServerAuthenticationSuccessHandler defaultSuccessHandler = new RedirectServerAuthenticationSuccessHandler("/");

		/**
		 * Configures the log in page to redirect to, the authentication failure page, and when authentication is
		 * performed. The default is that Spring Security will generate a log in page at "/login" and a log out page at
		 * "/logout". If this is customized:
		 * 
    *
  • The default log in & log out page are no longer provided
  • *
  • The application must render a log in page at the provided URL
  • *
  • The application must render an authentication error page at the provided URL + "?error"
  • *
  • Authentication will occur for POST to the provided URL
  • *
* @param loginPage the url to redirect to which provides a form to log in (i.e. "/login") * @return the {@link FormLoginSpec} to continue configuring * @see #authenticationEntryPoint(ServerAuthenticationEntryPoint) * @see #requiresAuthenticationMatcher(ServerWebExchangeMatcher) * @see #authenticationFailureHandler(ServerAuthenticationFailureHandler) */ public FormLoginSpec loginPage(String loginPage) { this.defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(loginPage); this.authenticationEntryPoint = this.defaultEntryPoint; this.requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage); //调用loginPage设置登陆链接时,会默认登陆失败的handler this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler(loginPage + "?error"); return this; } }

3.2、自定义登陆成功handler

import com.alibaba.fastjson.JSONObject;
import com.sisheng.authority.common.BaseResponse;
import com.sisheng.authority.common.Constants;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import reactor.core.publisher.Mono;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.security.handler
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-09 19:40
 */
public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
    @Override
    public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
        ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        BaseResponse baseResponse = new BaseResponse();
        baseResponse.setCode("success");
        baseResponse.setMsg("成功");
        String body = JSONObject.toJSONString(baseResponse);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Constants.ENCODING_UTF_8_CHARSET));
        return response.writeWith(Mono.just(buffer));
    }
}

3.3、自定义登陆失败handler

import com.alibaba.fastjson.JSONObject;
import com.sisheng.authority.common.BaseResponse;
import com.sisheng.authority.common.Constants;
import com.sisheng.authority.common.enums.ErrorEnum;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import reactor.core.publisher.Mono;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.security.handler
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-09 21:18
 */
public class JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {

    @Override
    public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
        ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
        if (exception instanceof UsernameNotFoundException) {
            return writeErrorMessage(response, ErrorEnum.USER_NOT_EXISTS);
        } else if (exception instanceof BadCredentialsException) {
            return writeErrorMessage(response, ErrorEnum.USERNAME_PASSWORD_ERROR);
        } else if (exception instanceof LockedException) {
            return writeErrorMessage(response, ErrorEnum.USER_LOCKED);
        }
        return writeErrorMessage(response, ErrorEnum.SYSTEM_ERROR);
    }

    private Mono writeErrorMessage(ServerHttpResponse response, ErrorEnum errorEnum) {
        BaseResponse baseResponse = new BaseResponse();
        baseResponse.setCode(errorEnum.getCode());
        baseResponse.setMsg(errorEnum.getMessage());
        String result = JSONObject.toJSONString(baseResponse);
        DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(Constants.ENCODING_UTF_8_CHARSET));
        return response.writeWith(Mono.just(buffer));
    }
}

4、security主配置类

import com.sisheng.authority.security.handler.JsonServerAuthenticationFailureHandler;
import com.sisheng.authority.security.handler.JsonServerAuthenticationSuccessHandler;
import com.sisheng.authority.service.security.impl.MysqlReactiveUserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
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.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import reactor.core.publisher.Mono;

import java.util.LinkedList;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.security.config
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-03 09:05
 */
@Configuration
/**
 * 启用webflux登陆权限校验 
 */
@EnableWebFluxSecurity
/**
 * 启用@PreAuthorize注解配置,如果不加这个注解的话,即使方法中加了@PreAuthorize也不会生效
 */
@EnableReactiveMethodSecurity
public class SecurityConfig {

    private static final String[] AUTH_WHITELIST = new String[]{"/login", "/actuator/**"};

    @Bean
    ReactiveAuthenticationManager reactiveAuthenticationManager() {
        final ReactiveUserDetailsService detailsService = userDetailsService();
        LinkedList managers = new LinkedList<>();
        managers.add(authentication -> {
            // 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error
            return Mono.empty();
        });
        // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
        managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(detailsService));
        return new DelegatingReactiveAuthenticationManager(managers);
    }

    /**
     * 将登陆后的用户及权限信息存入session中
     * @return
     */
    @Bean
    ServerSecurityContextRepository serverSecurityContextRepository() {
        return new WebSessionServerSecurityContextRepository();
    }

    /**
     * 密码加密工具
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义获取用户信息,此处使用mysql基于RBAC模式
     * @return
     */
    @Bean
    public ReactiveUserDetailsService userDetailsService() {
        return new MysqlReactiveUserDetailsServiceImpl();
    }

    /**
     * 此处的代码会放在SecurityConfig类中,此处只是摘要下
     * @param http
     * @return
     */
    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
        ServerHttpSecurity.FormLoginSpec formLoginSpec = http.formLogin();
        formLoginSpec.authenticationSuccessHandler(createAuthenticationSuccessHandler())
                .loginPage("/login")
                .authenticationFailureHandler(createAuthenticationFailureHandler());
        return formLoginSpec.and()
                .csrf().disable()
                .httpBasic().disable()
                .authorizeExchange()
                .pathMatchers(AUTH_WHITELIST).permitAll()
                .anyExchange().authenticated()
                .and().build();
    }

    /**
     * 登陆成功后执行的处理器
     * @return
     */
    private ServerAuthenticationSuccessHandler createAuthenticationSuccessHandler() {
        return new JsonServerAuthenticationSuccessHandler();
    }

    /**
     * 登陆失败后执行的处理器
     * @return
     */
    private ServerAuthenticationFailureHandler createAuthenticationFailureHandler() {
        return new JsonServerAuthenticationFailureHandler();
    }
}

四、基于lettuce响应式redis工具类

包括常用的set,get方法,操作hash的方法,执行lua脚本的方法,分布式锁的方法

import com.google.common.collect.Lists;
import com.sisheng.authority.cache.CacheComponent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveHashOperations;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;

/**
 * @author qinyong
 * @Title:
 * @Package com.sisheng.authority.cache.redis
 * @Description: created by IntelliJ IDEA
 * @date 2019-06-03 13:43
 */
@Slf4j
@Component
public class RedisComponent implements CacheComponent {

    @Autowired
    private ReactiveRedisTemplate redisTemplate;
    private static final String LOCK_KEY_PREFIX = "lock_";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Override
    @SuppressWarnings("unchecked")
    public  Mono getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Mono putValue(String key, V value, long time, ChronoUnit timeUnit) {
        return redisTemplate.opsForValue().set(key, value, Duration.of(time, timeUnit));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Mono putValue(String key, V value) {
        return redisTemplate.opsForValue().set(key, value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Mono delete(String key) {
        return redisTemplate.opsForValue().delete(key);
    }

    public  Mono putHashValue(String key, K hashKey, V hashValue) {
        @SuppressWarnings("unchecked")
        ReactiveHashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.put(key, hashKey, hashValue);
    }

    public  Mono getHashValue(String key, K hashKey) {
        @SuppressWarnings("unchecked")
        ReactiveHashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hashKey);
    }

    public  Mono> getMultiHashValue(String key, Collection hashKeys) {
        @SuppressWarnings("unchecked")
        ReactiveHashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.multiGet(key, hashKeys);
    }

    public  Flux getHashKeys(String key) {
        @SuppressWarnings("unchecked")
        ReactiveHashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.keys(key);
    }

    public  Mono deleteHashKeys(String key, Collection hashKeys) {
        if (CollectionUtils.isEmpty(hashKeys)) {
            return Mono.just(0L);
        }
        @SuppressWarnings("unchecked")
        ReactiveHashOperations opsForHash = redisTemplate.opsForHash();
        return opsForHash.remove(key, hashKeys.toArray());
    }

    /**
     * 执行lua脚本
     * @param luaScript 脚本内容
     * @param keys redis键列表
     * @param values 值列表
     * @return
     */
    @SuppressWarnings("unchecked")
    public Flux executeLuaScript(String luaScript, List keys, List values) {
        RedisScript redisScript = RedisScript.of(luaScript);
        return redisTemplate.execute(redisScript, keys, values);
    }

    /**
     * 执行lua脚本
     * @param luaScript 脚本内容
     * @param keys redis键列表
     * @param values 值列表
     * @param resultType 返回值类型
     * @return
     */
    @SuppressWarnings("unchecked")
    public  Flux executeLuaScript(String luaScript, List keys, List values, Class resultType) {
        RedisScript redisScript = RedisScript.of(luaScript, resultType);
        return redisTemplate.execute(redisScript, keys, values);
    }

    /**
     * 获取分布式锁
     * @param lockKey
     * @param requestId
     * @param expireTime
     * @return
     */
    @SuppressWarnings("unchecked")
    public Mono tryGetDistributedLock(String lockKey, String requestId, long expireTime) {
        Assert.hasLength(lockKey, "lockKey must not be empty");
        Assert.hasLength(requestId, "requestId must not be empty");
        return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, Duration.ofSeconds(expireTime));
    }

    /**
     * 释放分布式锁
     * @param lockKey
     * @param requestId
     * @return
     */
    public Mono releaseDistributedLock(String lockKey, String requestId) {
        Assert.hasLength(lockKey, "lockKey must not be empty");
        Assert.hasLength(requestId, "requestId must not be empty");
        String innerLockKey = buildLockKey(lockKey);

        return this.executeLuaScript(RELEASE_LOCK_SCRIPT, Lists.newArrayList(innerLockKey), Lists.newArrayList(requestId), Long.class)
                .next()
                .map(count -> count == 1)
                .doOnError(t -> log.error("release lockkey: [{}] failure", innerLockKey, t))
                .onErrorResume(e -> Mono.just(false))
                ;
    }

    /**
     * 添加前缀
     * @param lockKey
     * @return
     */
    public String buildLockKey(String lockKey) {
        Assert.hasLength(lockKey, "lockKey must not be empty");
        return LOCK_KEY_PREFIX + lockKey;
    }
}
 
   

 

转载于:https://my.oschina.net/u/2316420/blog/3061556

你可能感兴趣的:(springboot2 webflux集成spring security做权限登陆校验)