未經博主允許不得轉載:
項目優化重構,需要對原有的開發進行優化,網關模塊的校驗存在多個不同類型的校驗,為了使業務更加區分的清楚,使用策略模式對網關的校驗進行區分。
其場景為:對app1校驗會話token,對app2 校驗appid以及請求的簽名,對管理台校驗防重放攻擊,校驗nonce,時間戳等,同時為了以后進行業務的可擴展性,使用
注解實現策略模式。
由於在網關模塊中使用策略模式,為了提高代碼的可讀性,使用模板模式,便於代碼閱讀。
1.定義策略校驗的枚舉配置:
package com.example.demo.constant; public enum AuthStrategyEnum { PORTAL("portal"), WEI_XIN("weixin"), ALI("ali"); private String type; AuthStrategyEnum(String portal) { } public String getType(){ return type; } }
2.定義策略使用的注解
package com.example.demo.config; import com.example.demo.constant.AuthStrategyEnum; import java.lang.annotation.*; @Target(ElementType.TYPE) //作用在類上 @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited //子類可以繼承此注解 public @interface HandlerAuthStrategy { /** * 策略類型 * @return */ AuthStrategyEnum value(); }
3.定義獲取策略類型的容器配置
package com.example.demo.config; import com.example.demo.service.AuthStrategyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class AuthStrategyTypeConfig { @Autowired private ApplicationContext applicationContext; //存放所有策略類Bean的map public static Map<String, Class<AuthStrategyService>> strategyBeanMap= new HashMap(); /** * 根據類型獲取校驗類型的bean * @param type * @return */ public AuthStrategyService getAuthStrategy(String type){ Class<AuthStrategyService> strategyClass = strategyBeanMap.get(type); if(strategyClass==null) throw new IllegalArgumentException("沒有對應的校驗類型"); //從容器中獲取對應的策略Bean return applicationContext.getBean(strategyClass); } }
4.在spring啟動加載時,對使用策略注解的bean保存到map中,便於使用的時候直接獲取
package com.example.demo.config; import com.example.demo.service.AuthStrategyService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import java.util.Map; /** * 該類用於在spring啟動加載時,對使用策略注解的bean保存到map中,便於使用的時候直接獲取 */ public class HandlerAuthProcessor implements ApplicationContextAware { /** * 獲取所有的策略Beanclass 加入HandlerOrderContext屬性中 * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //獲取所有策略注解的Bean Map<String, Object> orderStrategyMap = applicationContext.getBeansWithAnnotation(HandlerAuthStrategy.class); orderStrategyMap.forEach((k,v)->{ Class<AuthStrategyService> orderStrategyClass = (Class<AuthStrategyService>) v.getClass(); String type = orderStrategyClass.getAnnotation(HandlerAuthStrategy.class).value().getType(); //將class加入map中,type作為key AuthStrategyTypeConfig.strategyBeanMap.put(type,orderStrategyClass); }); } }
5.定義策略實現的bean的接口,采用jdk8 提供的新特性,接口可以進行默認實現。
package com.example.demo.service; /** * 用於定義校驗的方法 */ public interface AuthStrategyService { /** * 校驗nonce值 */ default void checkNonce(String nonce){ // 默認實現校驗nonce值 } /** * 校驗時間戳 * @param timeStamp */ default void checkTimeStamp(long timeStamp){ // 默認實現校驗時間戳 } /** * 校驗appId * @param appId */ default void checkAppId(String appId){ // 默認實現 // todo查詢數據庫是否存在 } /** * 校驗token */ void checkToken(String token); }
6.對不同的策略類型進行業務實現。如果接口有默認實現,則可以直接使用,將獨有的校驗在自己的內部進行實現。並添加策略的注解。
package com.example.demo.service.impl; import com.example.demo.config.HandlerAuthStrategy; import com.example.demo.constant.AuthStrategyEnum; import com.example.demo.service.AuthStrategyService; import org.springframework.stereotype.Service; /** * 阿里接入校驗 */ @Service @HandlerAuthStrategy(value = AuthStrategyEnum.ALI) public class AliPayAuthStrategyServiceImpl implements AuthStrategyService { @Override public void checkToken(String token) { // 通過第三方的服務進行token校驗 } }
package com.example.demo.service.impl; import com.example.demo.config.HandlerAuthStrategy; import com.example.demo.constant.AuthStrategyEnum; import com.example.demo.service.AuthStrategyService; import org.springframework.stereotype.Service; /** * 管理台網關校驗實現 */ @Service @HandlerAuthStrategy(value = AuthStrategyEnum.PORTAL) public class PortalAuthStrategyServiceImpl implements AuthStrategyService { @Override public void checkAppId(String appId) { } @Override public void checkToken(String token) { // 管理台不需要校驗token } }
package com.example.demo.service.impl; import com.example.demo.config.HandlerAuthStrategy; import com.example.demo.constant.AuthStrategyEnum; import com.example.demo.service.AuthStrategyService; import org.springframework.stereotype.Service; /** * 微信app校驗 */ @Service @HandlerAuthStrategy(value = AuthStrategyEnum.WEI_XIN) public class WeixinAuthStrategyServiceImpl implements AuthStrategyService { @Override public void checkToken(String token) { // jwt解析校驗token有效性 } }
7.在過濾器中采用模版+策略的方式進行接口認證校驗
package com.example.demo.filter; import com.example.demo.config.AuthStrategyTypeConfig; import com.example.demo.mapper.AuthRequestMapper; import com.example.demo.service.AuthStrategyService; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.http.HttpServletRequest; public class AuthFilter { @Autowired private AuthStrategyTypeConfig authStrategyTypeConfig; @Autowired private AuthRequestMapper dbMapper; public void filter(HttpServletRequest request){ // 解析當前請求的路徑 String requestPath = request.getRequestURI(); String appId = request.getHeader("appId"); String nonce = request.getHeader("nonce"); String timestamp = request.getHeader("timestamp"); String token = request.getHeader("token"); // 從數據庫查詢app對應的接口的配置策略類型 String strategyType = dbMapper.getAuthStrategy(requestPath,appId); // 根據type獲取當前的校驗類型 AuthStrategyService service = authStrategyTypeConfig.getAuthStrategy(strategyType); // 配置校驗模版。會根據配置的策略bean進行不同的校驗, // 接口定義的時候,對共有的校驗則進行默認實現,只需要對獨有的校驗獨自實現即可 service.checkAppId(appId); service.checkNonce(nonce); service.checkAppId(timestamp); service.checkToken(token); } }