SpringCloud学习之Gateway—单点登录的实现
SpringCloud学习之Gateway——单点登录的实现
1.Gateway的介绍
路由(Route): 路由是网关的基本组成部分,路由信息由ID、目标URL、一组断言和一组过滤器组成,如果断言为真,则说明请求的URL和配置匹配。
断言(Predicate): Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者自定义匹配来自于Http Request中的任何信息,比如请求头和参数等
过滤器(Flute): 一个标准的SpringWebFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Globa lFilter,过滤器将会对请求和响应进行处理。
2.Gateway的作用
Gateway是SpringCloud提供的API网关,提供主要功能有:路由和鉴权以及限流、统一配置等。
3.Gateway的工作原理
1.客户端发送请求到网关
2.网关通过HandlerMapping处理器映射器获得Handler处理器
3.Handler执行通过网关内部的过滤器链,过滤器分为两种:pre前置、post后置
4.前置过滤器主要实现鉴权和路由,后置过滤器可以进行性能监控和数据统计等
5.通过所有过滤器才能访问到需要的服务
4.Gateway的路由功能
使用过程:
1.创建网关项目
2.引入geteway依赖
3.网关需要注册到注册中心
5.配置路由
spring
cloud:
gateway:
routes: #路由
- id: 路由id
uri: lb: //order-service-ruter #服务名
predicates: #断言
- Path=/order/**,/orders/**
- id: 路由id
uri: lb: //product-service-ruter #服务名
predicates: #断言
- Path=/product/**,/products/**
5.Gateway跨域配置
通过网关统一跨域,其它服务中不配置跨域
spring:
application:
name: gateway-service
cloud:
gateway:
globalcors:
cors-configurations: # 跨域配置
'[/**]': # 匹配所有路径
allowed-origins: # 允许的域名
- "http://localhost:8080"
allowed-headers: "*" # 允许的请求头
allowed-methods: "*" # 允许的方法
allow-credentials: true # 是否携带cookie
6.Gateway过滤器
从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:
生命周期时机点 | 作用 |
---|---|
pre | 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 |
post | 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。 |
从过滤器类型的角度,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种
过滤器类型 | 影响范围 |
---|---|
GateWayFilter | 应用到单个路由上 |
GlobalFilter | 应用到所有的路由上 |
7.使用Gateway实现单点登录
单点登录(Single Sign On),简称为 SSO,在分布式架构项目中,只需要在一个节点进行登录验证,就能够在其它的所有相关节点实现访问。
实现方案:
- JWT+Gateway方案
- OAuth2方案
- 共享session
以下是使用JWT+Gateway方案实现单点登录
实现步骤:
1.创建数据库,数据表(商品,订单,用户)
2.创建用户服务,添加所需依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.blb</groupId>
<artifactId>comment_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.注册到Eureka上(启动类加注解@EnableDiscoveryClient、写配置文件)
server.port=10000
#服务名
spring.application.name=mysql-user-service
#是否从注册中心获得数据
eureka.client.register-with-eureka=true
#是否注册到注册中心上
eureka.client.fetch-registry=true
#配置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8888/eureka/
4.配置数据源和mybatis-plus
#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/feigin_user?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
mybatis-plus.type-aliases-package=com.blb.comment.entity
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath:mapper/*.xml
5.编写entity、mapper、service
略…
6.编写UserDetailsService实现类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//按用户名查询
TUser user = userMapper.selectOne(new QueryWrapper<TUser>().lambda().eq(TUser::getUsername, s));
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
//返回正确的用户信息
return new org.springframework.security.core.userdetails.User(s,user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
7.编写Security配置,登录成功的处理器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置自定义登录逻辑
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置放行url
http.authorizeRequests()
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**","/*/api-docs"
,"/login","/logout").permitAll()
.anyRequest().authenticated() //配置其它url要验证
.and()
.formLogin() //配置登录相关
.successHandler(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
}
}
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, 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.在网关配置用户的路由
server:
port: 9999
eureka:
client:
fetch-registry: true
register-with-eureka: true
serviceUrl:
defaultZone: http://127.0.0.1:8888/eureka/
# 网关配置
spring:
application:
name: mysql-gateway-server
cloud:
gateway:
routes:
- id: order-server-route #订单路由
uri: lb://mysql-order-server #订单服务名称
predicates: #断言
- Path=/orderlist/**,/order/** #匹配路径
- id: product-server-route #商品路由
uri: lb://mysql-product-server #商品服务名称
predicates: #断言
- Path=/productlist/**,/product/** #匹配路径
- id: user-server-route #用户路由
uri: lb://mysql-user-service #用户服务名称
predicates: #断言
- Path=/login,/logout,/user/** #匹配路径
globalcors:
cors-configurations: # 跨域配置
'[/**]': # 匹配所有路径
allowed-origins: # 允许的域名(前端启动端口)
- "http://localhost:8080"
allowed-headers: "*" # 允许的请求头
allowed-methods: "*" # 允许的方法
allow-credentials: true # 是否携带cookie
user:
white-list: # 白名单
- /login
- /logout
9.在gateway中编写WhiteListConfig(开发放行接口的白名单)
@Data
@Configuration
@ConfigurationProperties(prefix = "user")
public class WhiteListConfig {
//放行白名单
private List<String> whiteList;
}
10.编写网关过滤器(验证用户token)
/**
* 用户验证过滤器
* @author Charon
*/
@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;
}
}
使用springboot+eureka+feign+gateway+vue实现单点登录后对商品、订单进行CRUD,具体代码详见gitee仓库地址
更多推荐
所有评论(0)