1.共享cookie实现单点登录
1.1 单系统登录的解决方案是cookie,cookie携带会话id在浏览器与服务器之间维护会话状态。但是cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有cookie。
1.2 共享cookie的方式存在众多局限: 1)首先,应用群域名得统一; 2)应用群各系统使用的技术(至少是web服务器)要相同,不然cookie的key变量(tomcat为JSESSIONID)不同,无法维持会话,共享cookie的方式是无法实现跨语言技术平台登录的,比如java、php、.net系统之间; cookie本身不安全。
2.单点登录SSO
2.1 什么是单点登录:
单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分
2.2 sso登录流程:
sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息。sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同
2.3 sso注销流程 单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁
用户向系统1发起注销请求
系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
sso认证中心向所有注册系统发起注销请求
各注册系统接收sso认证中心的注销请求,销毁局部会话
sso认证中心引导用户至登录页面
3.JWT单点登录
以上的session还有token的方案,在集群环境下,都是靠第三方缓存数据库redis来实现数据的共享。 跟以上那些唯一的不同点就是:token存放了用户的基本信息,更直观一点就是将原本放入redis中的用户数据,放入到token中去了!
JWT相比session方案,因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA、JavaScript、PHP等很多语言都可以使用,而session方案只针对JAVA。
因为有了payload部分,所以JWT可以存储一些其他业务逻辑所必要的非敏感信息。
4.Gateway
1.使用Route结合Hystrix实现默认降级策略 2.使用GatewayFilter接口,自定义过滤器类,实现登录态(token)校验
前端请求时path带/gateway/,在gateway层使用StripPrefix=1,去掉gateway,最终微服务上的path不带"/gateway/". 使用Hystrix实现默认降级策略,降级接口实现如下:
@Slf4j
@RestController
public class DefaultHystrixController {
@RequestMapping("/defaultfallback")
public ApiResult defaultfallback(){
log.info("服务降级中");
return ApiResult.failure("服务异常");
}
}
4.1 实现登录态(token)校验-自定义过滤器,实现GatewayFilter, Ordered 2个接口。
import com.*.auth.UserTokenTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description token过滤器
*/
@Slf4j
@Component
public class LoginTokenFilter implements GatewayFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "Authorization";
private static final String BEARER = "Bearer ";
/**
* token过滤
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("当前环境已开启token校验");
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
ServerHttpResponse response = exchange.getResponse();
// 取Authorization
String tokenHeader = headers.getFirst(AUTHORIZE_TOKEN);
log.info("tokenHeader=" + tokenHeader);
// token不存在
if (StringUtils.isEmpty(tokenHeader)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 取token
String token = this.getToken(tokenHeader);
log.info("token=" + token);
// token不存在
if (StringUtils.isEmpty(token)) {
log.info("token不存在");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 校验 token是否失效
if (UserTokenTools.isTokenExpired(token, null)) {
log.info("token失效");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 校验 token是否正确
if (!UserTokenTools.checkToken(token, null)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// //有token 这里可根据具体情况,看是否需要在gateway直接把解析出来的用户信息塞进请求中,我们最终没有使用
// UserTokenInfo userTokenInfo = UserTokenTools.getUserTokenInfo(token);
// log.info("token={},userTokenInfo={}",token,userTokenInfo);
// request.getQueryParams().add("token",token);
//request.getHeaders().set("token", token);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -10;
}
/**
* 解析Token
*/
public String getToken(String requestHeader) {
//2.Cookie中没有从header中获取
if (requestHeader != null && requestHeader.startsWith(BEARER)) {
return requestHeader.substring(7);
}
return "";
}
}
4.2配置路由,可根据具体情况,如果只有一套登录态,那就用一个filter即可。
import com.*.gateway.filter.AuthorizeGatewayFilter;
import com.*.gateway.filter.LoginTokenFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator getRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// token校验1
.route(predicateSpec -> predicateSpec
.path("/gateway/pay/card/**", "/gateway/app/**")
.filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new AuthorizeGatewayFilter()))
.uri("lb://OLOAN-PAY-SERVICE")
.id("OLOAN-PAY-SERVICE-token"))
// token校验2
.route(predicateSpec -> predicateSpec
.path("/gateway/order-audit/**", "/gateway/order/**", "/gateway/order-payment/**")
.filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new LoginTokenFilter()))
.uri("lb://OLOAN-ORDER-SERVICE")
.id("OLOAN-ORDER-ORDER-token"))
.build();
}
}
yml参考
spring:
cloud:
gateway:
discovery:
locator:
enabled: false
#开启小写验证,默认feign根据服务名查找都是用的全大写
lowerCaseServiceId: true
default-filters:
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
routes:
- id: OLOAN-FINANCIAL-PRODUCT-SERVICE
# lb代表从注册中心获取服务
uri: lb://OLOAN-FINANCIAL-PRODUCT-SERVICE
predicates:
# 转发该路径
- Path=/gateway/financialProduct/**
# 带前缀
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/defaultfallback
- id: ADMIN-SERVICE
uri: lb://ADMIN-SERVICE
predicates:
- Path=/gateway/auth/**
filters:
- StripPrefix=2
- name: Hystrix
args:
name: fallbackcmd
|