微服務下使用OAuth2實現網關安全

微服務下Spring Security實現Oauth2協議 入門篇我們介紹瞭如何在微服務下基於OAuth2協議構建認證服務器和資源服務器。資源服務器會檢查請求頭裏面是否帶上了token,並去認證服務器校驗這個token是否合法,是否過期,是否有權限做對應的操作。顯然隨着微服務數量的增長,在每一個微服務上都要做資源服務器的配置實不可取的,因此我們需要把這部分的操作交由網關去處理。在上一節中check_token這部分的工作,資源服務器利用RemoteTokenServices幫我們實現了,但是這一節中我們需要在網關上自己手動實現這部分的邏輯。

OAuth2 網關安全架構圖

網關邏輯上是資源服務器,需要訂單服務,庫存服務不再作爲資源服務器。
在這裏插入圖片描述

認證服務器增加網關的配置

在這裏插入圖片描述
上一節中,訂單服務的資源服務器配置相關代碼可以刪除了
在這裏插入圖片描述

新增網關服務

添加依賴

	<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>

開啓@EnableZuulProxy

@SpringBootApplication
@EnableZuulProxy
public class GatewayServer {
	public static void main(String[] args) {
		SpringApplication.run(GatewayServer.class, args);
	}

}

配置路由規則application.yml

zuul:
  routes:
    token:
      url: http://localhost:9090
    order:
      url: http://localhost:9080
    price:
      url: http://localhost:9060
  sensitive-headers:
/**
 * 定義zuul pre前置過濾器
 *
 */
@Slf4j
@Component
public class OAuthFilter extends ZuulFilter {
	
	private RestTemplate restTemplate = new RestTemplate();

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		
		log.info("oauth start");
		// 爲了獲取request請求
		RequestContext requestContext = RequestContext.getCurrentContext();
		HttpServletRequest request = requestContext.getRequest();

		// 獲取token請求不進行過濾
		if(StringUtils.startsWith(request.getRequestURI(), "/token")) {
			return null;
		}
		
		String authHeader = request.getHeader("Authorization");
		
		if(StringUtils.isBlank(authHeader)) {
			return null;
		}

		// 如果請求頭帶了以barer開頭的請求,則去認證服務器校驗token
		if(!StringUtils.startsWithIgnoreCase(authHeader, "bearer ")) {
			return null;
		}
		
		try {

			// 將token放入請求頭裏
			TokenInfo info = getTokenInfo(authHeader);
			request.setAttribute("tokenInfo", info);
			
		} catch (Exception e) {
			log.error("get token info fail", e);
		}
		
		return null;
	}

	private TokenInfo getTokenInfo(String authHeader) {
		
		String token = StringUtils.substringAfter(authHeader, "bearer ");
		String oauthServiceUrl = "http://localhost:9090/oauth/check_token";
		
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
		headers.setBasicAuth("gateway", "123456");
		
		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.add("token", token);
		
		HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
		
		ResponseEntity<TokenInfo> response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);
		
		log.info("token info :" + response.getBody().toString());
		
		return response.getBody();
	}

	/**
	 * zuul 提供了四種過濾器類型
	 * pre
	 * post
	 * error
	 * route
	 * @return
	 */
	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 1;
	}

}

AuthorizationFilter

/**
 *
 * 授權過濾器
 */
@Slf4j
@Component
public class AuthorizationFilter extends ZuulFilter {

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		
		log.info("authorization start");
		
		RequestContext requestContext = RequestContext.getCurrentContext();
		HttpServletRequest request = requestContext.getRequest();
		
		if(isNeedAuth(request)) {

			// OAuthFilter token 校驗通過,會將token信息放在請求頭裏
			TokenInfo tokenInfo = (TokenInfo)request.getAttribute("tokenInfo");

			// 校驗是否有權限
			if(tokenInfo != null && tokenInfo.isActive()) {
				if(!hasPermission(tokenInfo, request)) {
					log.info("audit log update fail 403");
					handleError(403, requestContext);
				}
				
				requestContext.addZuulRequestHeader("username", tokenInfo.getUser_name());
			}else {
				if(!StringUtils.startsWith(request.getRequestURI(), "/token")) {
					log.info("audit log update fail 401");
					handleError(401, requestContext);
				}
			}
		}
		
		return null;
	}
	
	private void handleError(int status, RequestContext requestContext) {
		requestContext.getResponse().setContentType("application/json");
		requestContext.setResponseStatusCode(status);
		requestContext.setResponseBody("{\"message\":\"auth fail\"}");
		requestContext.setSendZuulResponse(false);
	}

	private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
		return true; //RandomUtils.nextInt() % 2 == 0;
	}

	private boolean isNeedAuth(HttpServletRequest request) {
		return true;
	}

	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 3;
	}

}

代碼結構
在這裏插入圖片描述

postman測試

網關basic認證
請求token
請求訂單服務,帶上token

網關限流

網關上的限流工作只做適合粗粒度的限流,不要做業務上的線流,網關與網關後面的微服務之間需要是低耦合的。因爲業務規則改變了,需要調整網關上的限流規則,甚至要重新部署,這是不可取的。
這一小節內容主要介紹網關上如何做限流操作,這裏我們使用了開源組件spring-cloud-zuul-ratelimit

引入依賴:

	<dependency>
			<groupId>com.marcosbarbero.cloud</groupId>
			<artifactId>spring-cloud-zuul-ratelimit</artifactId>
			<version>2.2.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

限流規則配置:

  # 限流的配置 這裏存在數據庫中,生產環境時候 需要放在redis中
#  ratelimit:
#    enabled: true
#    repository: JPA
#    default-policy-list:
#    - limit: 2
#      quota: 1
#      refresh-interval: 3
#      type:
#        - url  /a get # 針對指定的url進行限制
#        - httpmethod
#  jpa:
#    generate-ddl: true
#    show-sql: true

網關用到了數據庫,因此還有數據源的配置,這裏不貼出來了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章