注解实现策略模式
未经博主允许不得转载:
项目优化重构,需要对原有的开发进行优化,网关模块的校验存在多个不同类型的校验,为了使业务更加区分的清楚,使用策略模式对网关的校验进行区分。
其场景为:对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); } }