分布式开发时,微服务会有很多,但是网关是请求的第一入口,所以一般会把客户端请求的权限验证统一放在网关进行认证与鉴权。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
WebFlux | SpringMVC |
---|---|
@EnableWebFluxSecurity | @EnableWebSecurity |
ReactiveSecurityContextHolder | SecurityContextHolder |
AuthenticationWebFilter | FilterSecurityInterceptor |
ReactiveAuthenticationManager | AuthenticationManager |
ReactiveUserDetailsService | UserDetailsService |
ReactiveAuthorizationManager | AccessDecisionManager |
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、自定义授权失败处理类
public class AccessDeniedHandlerImpl implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, AccessDeniedException e) {
ServerHttpResponse response = serverWebExchange.getResponse();
response.getHeaders().add("Content-Type","application/json;charset=utf-8");
ResultVO<Object> result = ResultVO.result("无权限进行此操作", ResultCode.NOACCESS, null);
String jsonString = JSON.toJSONString(result);
DataBuffer wrap = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(wrap));
}
}
3、自定义认证失败处理类
public class AuthenticationEntryPointImpl implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange serverWebExchange, AuthenticationException e) {
ServerHttpResponse response = serverWebExchange.getResponse();
response.getHeaders().add("Content-Type","application/json;charset=utf-8");
ResultVO<Object> result = ResultVO.result("认证失败或已在别处登录,请进行登录", ResultCode.UNAUTHORIZED, null);
String jsonString = JSON.toJSONString(result);
DataBuffer wrap = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(wrap));
}
}
4、获取请求头中带过来的token值,解析并认证用户信息
@Component
public class TokenSecurityContextRepository implements ServerSecurityContextRepository {
@Autowired
private RoleMapper roleMapper;
/**
* 由于无状态,所以不需要保存用户登录状态
* @param serverWebExchange
* @param securityContext
* @return
*/
@Override
public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
return Mono.empty();
}
/**
* 对用户身份进行验证
* @param exchange
* @return
*/
@Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
// 从请求头中获取jwt
String authToken = exchange.getRequest().getHeaders().getFirst("login-token");
// 如果请求头没有token,直接放行,交给下面的过滤器处理
// 如果当前请求的接口需要认证或权限,下面的过滤器会拦截
if (Objects.isNull(authToken) || !StringUtils.hasText(authToken)){
return Mono.empty();
}
// 验证token,以及redis中是否存在该token
if (JwtUtil.verifyToken(authToken)){
Map<String, String> payload = JwtUtil.getPayload(authToken);
String id = payload.get("id");
String sub = payload.get("sub");
String s = RedisFactory.select(0).opsForValue().get(sub);
// 验证jwt中的id和redis中的id是否一致
if (!Objects.isNull(s) && s.equals(id)) {
//完成续签,刷新token的有效时间
RedisFactory.select(0).expire(sub, 100000, TimeUnit.SECONDS);
//获取用户权限信息
List<String> list = roleMapper.selectUserRolesByEmail(sub);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (String authority : list){
authorities.add(new SimpleGrantedAuthority(authority));
}
SecurityContext securityContext = new SecurityContextImpl();
// 创建一个认证对象,第一个参数为用户信息,第三个参数为用户权限
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(sub,null,authorities);
// 传入认证对象到SecurityContext中,证明该用户已经认证
securityContext.setAuthentication(token);
return Mono.justOrEmpty(securityContext);
}
}
return Mono.empty();
}
}
5、SercurityConf配置类
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig{
@Autowired
private TokenSecurityContextRepository tokenSecurityContextRepository;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.formLogin().disable()
.logout().disable()
.securityContextRepository(tokenSecurityContextRepository)
.authorizeExchange()
.pathMatchers(HttpMethod.GET).permitAll() // 放开所有get请求
.pathMatchers("/auth-server/user/login").permitAll() // 放开账密登录请求
.anyExchange().authenticated() //其余请求都需要验证
.and()
.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPointImpl()) //授权失败处理器
.accessDeniedHandler(new AccessDeniedHandlerImpl()); // 验证失败处理器
return http.build();
}
}