Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
1.共享cookie實現單點登錄
1.1 單系統登錄的解決方案是cookie,cookie攜帶會話id在瀏覽器與服務器之間維護會話狀態。但是cookie是有限制的,這個限制就是cookie的域(通常對應網站的域名),瀏覽器發送http請求時會自動攜帶與該域匹配的cookie,而不是所有cookie。
1.2 共享cookie的方式存在眾多局限:
1)首先,應用群域名得統一;
2)應用群各系統使用的技術(至少是web服務器)要相同,不然cookie的key變量(tomcat為)不同,無法維持會話,共享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、、PHP等很多語言都可以使用,而session方案只針對JAVA。
因為有了payload部分,所以JWT可以存儲一些其他業務邏輯所必要的非敏感信息。
4.Gateway
1.使用Route結合Hystrix實現默認降級策略
2.使用接口,自定義過濾器類,實現登錄態(token)校驗
前端請求時path帶/gateway/,在gateway層使用=1,去掉gateway,最終微服務上的path不帶"/gateway/".
使用Hystrix實現默認降級策略,降級接口實現如下:
@Slf4j
@RestController
public class DefaultHystrixController {
@RequestMapping("/defaultfallback")
public ApiResult defaultfallback(){
log.info("服務降級中");
return ApiResult.failure("服務異常");
}
}
4.1 實現登錄態(token)校驗-自定義過濾器,實現, 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 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
*請認真填寫需求信息,我們會在24小時內與您取得聯系。