注解实现策略模式

未经博主允许不得转载:

  项目优化重构,需要对原有的开发进行优化,网关模块的校验存在多个不同类型的校验,为了使业务更加区分的清楚,使用策略模式对网关的校验进行区分。

其场景为:对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);
    }
}

 

posted @ 2021-02-28 21:43  香吧香  阅读(785)  评论(0编辑  收藏  举报