Gateway的作用
Gateway是SpringCloud提供的API网关,提供主要功能有:路由和鉴权以及限流、统一配置等。
Gateway的工作原理
1.客户端发送请求到网关。
2.网关通过HandlerMapping处理器映射获得Handler处理器。
3.Handler执行通过网关内部的过滤器链,过滤器分为两种:pre前置、post后置。
4.前置过滤器主要实现鉴权和路由,后置过滤器可以进行性能监控和数据统计等。
5. 通过所有过滤器才能访问到需要的服务。
Gateway的路由功能
使用过程:
1、创建网关项目
2、 引入gateway依赖
3、 网关需要注册到注册中心
4、 配置路由
spring:
cloud:
gateway:
routes: # 路由
- id: order-service-route
uri: lb://order-service
predicates: # 断言
- Path=/order/**,/orders/**
- id: product-service-route
uri: lb://product-service
predicates: # 断言
- Path=/product/**,/products/**
Gateway跨域配置
通过网关统一跨域,其它服务中不配置跨域。
spring:
application:
name: gateway-service
cloud:
gateway:
globalcors:
cors-configurations: # 跨域配置
'[/**]': # 匹配所有路径
allowed-origins: # 允许的域名
- "http://localhost:8080"
allowed-headers: "*" # 允许的请求头
allowed-methods: "*" # 允许的方法
allow-credentials: true # 是否携带cookie
Gateway过滤器
从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:
从过滤器类型的角度,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种。
单点登录的实现
单点登录(Single Sign On),简称为 SSO,在分布式架构项目中,只需要在一个节点进行登录验证,就能够在其它的所有相关节点实现访问。
实现方案:
1、JWT+Gateway方案
2、OAuth2方案
3、共享session
JWT+Gateway方案
实现步骤:
1、创建用户数据库,用户表
2、创建用户服务,添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.blb</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>common_api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3、注册到Eureka上:在启动类上加注解
@EnableDiscoveryClient
4、配置数据源和mybatis-plus
5、编写entity、mapper、service
6、编写UserDetailsService实现类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//按用户名查询
User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getUsername, s));
if(user==null){
throw new UsernameNotFoundException("用户名不存在");
}
//返回正确的用户信息
return new org.springframework.security.core.userdetails.User(s,user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
7、编写Security配置类,登录成功的处理器生成token
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 用来记录账号,密码,角色信息。
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置自定义登录逻辑
auth.userDetailsService(userDetailsService);
}
/**
* 也就是对角色的权限——所能访问的路径做出限制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置放行url
http.authorizeRequests()
.antMatchers("/login","/logout","/swagger-ui.html","/swagger-recources/**","/webjars/**","/api-docs").permitAll()
.anyRequest().authenticated() //配置其它url要验证
.and()
.formLogin() //配置登录相关
.successHandler(new LoginSuccessHandler()) //配置登录成功的处理器
.failureHandler((req,resp,auth) -> { //配置登录失败的处理器
ResponseResult.write(resp, ResponseResult.error(ResponseStatus.LOGIN_ERROR));
})
.and()
.exceptionHandling()
.authenticationEntryPoint((req,resp,auth) ->{ //配置拦截未登录请求的处理
ResponseResult.write(resp,ResponseResult.error(ResponseStatus.AUTHENTICATE_ERROR));
})
.and()
.logout()
.logoutSuccessHandler((req,resp,auth) ->{ //配置登出处理器
ResponseResult.write(resp,ResponseResult.ok("注销成功"));
})
.clearAuthentication(true) //清除验证缓存
.and()
.csrf()//跨站攻击手段
.disable() //关闭csrf保护
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); //不使用session
}
}
登录成功的处理器
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获得用户名
User user = (User) authentication.getPrincipal();
//将用户名生成jwt token
String token = JwtUtil.generateToken(user.getUsername(), RsaUtil.privateKey, JwtUtil.EXPIRE_MINUTES);
//将token 发送给前端
UserTokenVO userTokenVo = new UserTokenVO(user.getUsername(), token);
ResponseResult.write(response, ResponseResult.ok(userTokenVo));
log.info("user:{} token:{}", user.getUsername(), token);
}
}
8、在网关配置用户的路由
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/login,/logout,/user/**
9、开发放行接口的白名单
user:
white-list: # 自定义白名单
- /login
- /logout
10、编写一个配置类区扫描配置文件中的白名单
@Data
@Configuration
@ConfigurationProperties(prefix = "user")
public class WhiteListConfig {
private List<String> whiteList;
}
11、编写一个网关的过滤器
/**
* 用户验证过滤器
*/
@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private WhiteListConfig whiteListConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获得请求和响应对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//对白名单中的地址放行
List<String> whiteList = whiteListConfig.getWhiteList();
for(String str : whiteList){
if(request.getURI().getPath().contains(str)){
log.info("白名单,放行{}",request.getURI().getPath());
return chain.filter(exchange);
}
}
//获得请求头中Authorization token信息
String token = request.getHeaders().getFirst("Authorization");
try{
//解析token
String username = JwtUtil.getUsernameFromToken(token, RsaUtil.publicKey);
log.info("{}解析成功,放行{}",username,request.getURI().getPath());
return chain.filter(exchange);
}catch (Exception ex){
log.error("token解析失败",ex);
//返回验证失败的响应信息
response.setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer wrap = response.bufferFactory().wrap("验证错误,需要登录".getBytes());
return response.writeWith(Mono.just(wrap));
}
}
@Override
public int getOrder() {
return 0;
}
}