自定義註解實現列維度的數據權限控制(springboot + shrio)

需求描述:
傳統項目中,經常會遇到系統根據用戶權限的不同,前臺部分數據需要打碼或者直接隱藏。而且後臺返回的數據也需要模糊處理。

一、思路和要求

思路: 後端會經過序列化然後傳輸到前臺,我們需要在序列化的時候針對這些需要打碼的字段進行特殊處理。
要求: 實現需要有通用性,且最好能做到所有有權限控制的字段在序列化時都會經過我們的權限判斷進行統一處理。

二、SpringBoot + Jackson序列化

1. 自定義序列化配置

@Configuration
public class TmallConfiguration {

    @Autowired
    private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;

    @PostConstruct
    public void init() {
        ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper();
        objectMapper.setSerializerFactory(objectMapper.getSerializerFactory()
                .withSerializerModifier(new TmallBeanSerializeModifier()));
    }

}
    

2. 自定義註解


package net.mshome.twisted.tmall.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

/**
 * 權限控制註解,根據權限給字段打馬賽克
 *
 * @author [email protected]
 * @date 2020/3/2
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD})
public @interface PermissionControlled {

    String NULL = "null";

    /**
     * 可訪問的權限集合
     */
    String[] include() default {};

    /**
     * 不可訪問的權限集合
     */

    String[] exclude() default {};

    /**
     * 針對大多數字段的默認值,接受一個String類型參數,優先級較{@link #supplier()}低
     */
    String defaultValue() default NULL;

    /**
     * 自定義默認值提供者,優先級較{@link #defaultValue()}高
     */
    Class<? extends DefaultValueSupplier> supplier() default NullValueSupplier.class;

}




3. 結合自定義註解修改字段的序列化過程

package net.mshome.twisted.tmall.aop.configuration;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import net.mshome.twisted.tmall.annotation.NullValueSupplier;
import net.mshome.twisted.tmall.annotation.PermissionControlled;
import net.mshome.twisted.tmall.annotation.DefaultValueSupplier;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 自定義序列化,結合shiro加入權限控制
 *
 * @author [email protected]
 * @date 2020/3/2
 */
public class TmallBeanSerializeModifier extends BeanSerializerModifier {

    private static boolean isInclude(Subject subject, String[] include) {
        if (Objects.isNull(subject) || include.length == 0) {
            return true;
        }
        return Arrays.stream(include).anyMatch(subject::isPermitted);
    }

    private static boolean isExclude(Subject subject, String[] exclude) {
        if (Objects.isNull(subject) || exclude.length == 0) {
            return true;
        }
        return Arrays.stream(exclude).anyMatch(subject::isPermitted);
    }


    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                                                     List<BeanPropertyWriter> beanProperties) {
        // 將有權限控制註解的字段單獨處理
        for (BeanPropertyWriter beanPropertyWriter : beanProperties) {
            PermissionControlled controlled = beanPropertyWriter.findAnnotation(PermissionControlled.class);
            if (Objects.isNull(controlled) || (controlled.include().length == 0 && controlled.exclude().length == 0)) {
                continue;
            }
            beanPropertyWriter.assignSerializer(new PermissionControlledJsonSerializer(beanPropertyWriter));
        }
        return beanProperties;
    }

    private static class PermissionControlledJsonSerializer extends JsonSerializer<Object> {

        private BeanPropertyWriter propertyWriter;

        private static final NullValueSupplier DEFAULT_VALUE_SUPPLIER = new NullValueSupplier();

        public PermissionControlledJsonSerializer(BeanPropertyWriter propertyWriter) {
            this.propertyWriter = propertyWriter;
        }

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
                throws IOException {
            PermissionControlled controlled = propertyWriter.findAnnotation(PermissionControlled.class);
            Subject subject = SecurityUtils.getSubject();
            String[] include = controlled.include();
            String[] exclude = controlled.exclude();

            // 無用戶,敏感數據,直接打碼
            if (Objects.isNull(subject)) {
                jsonGenerator.writeNull();
                return;
            }

            // 有權限則寫入真實的值
            if (!isExclude(subject, exclude) && isInclude(subject, include)) {
                jsonGenerator.writeObject(o);
                return;
            }

            // 否則,打馬賽克
            Class<? extends DefaultValueSupplier> supplier = controlled.supplier();
            if (NullValueSupplier.class != supplier) { // 配置了默認值提供者
                jsonGenerator.writeObject(BeanUtils.instantiateClass(supplier).supply());
            } else if (!Objects.equals(PermissionControlled.NULL, controlled.defaultValue())) { // 配置了默認值String
                jsonGenerator.writeString(controlled.defaultValue());
            } else if (propertyWriter.getType().isArrayType()) { // 如果數組類型則返回空數組
                jsonGenerator.writeStartArray();
                jsonGenerator.writeEndArray();
            } else if (propertyWriter.getType().isCollectionLikeType()) { // 如果是集合則返回空集合
                jsonGenerator.writeObject(Collections.emptyList());
            } else { // 否則執行默認的默認值提供者
                jsonGenerator.writeObject(DEFAULT_VALUE_SUPPLIER.supply());
            }

        }

    }

}











此處使用shiro權限控制,如果沒用shiro也一樣的,拿session中的權限進行判斷即可。shiro中的isPermitted判斷一次好幾十毫秒,建議將權限放到session中,從緩存中拿權限進行判斷。

4. 自定義馬賽克

package net.mshome.twisted.tmall.annotation;

/**
 * 默認值提供者接口
 *
 * @author [email protected]
 * @date 2020/3/3
 */
public interface DefaultValueSupplier {

    Object supply();

}

5. 默認的馬賽克 null

package net.mshome.twisted.tmall.annotation;

/**
 * 默認的無權限返回值提供者
 *
 * @author [email protected]
 * @date 2020/3/3
 */
public class NullValueSupplier implements DefaultValueSupplier {

    @Override
    public Object supply() {
        return null;
    }

}



三、如何使用

package net.mshome.twisted.tmall.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import net.mshome.twisted.tmall.annotation.PermissionControlled;
import net.mshome.twisted.tmall.annotation.ProductNameDefaultValueSupplier;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 產品實體類
 *
 * @author [email protected]
 * @since 2019-08-26
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Product extends BaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    // @PermissionControlled(include = "admin", exclude = "user", supplier = ProductNameDefaultValueSupplier.class)
    // @PermissionControlled(include = "admin", exclude = "user", defaultValue = "默認的產品名稱")
    private String name;

    private Integer categoryId;

    private String briefIntroduction;

    private String detailIntroduction;

    private BigDecimal originalPrice;

    private BigDecimal promotePrice;

}

此註解,直接註解在字段上即可,支持2種默認值提供方式,普通String則直接使用defaultValue提供即可,如果爲字段爲複雜對象(比如枚舉),則可用使用自定義默認值提供器。如下所示。


package net.mshome.twisted.tmall.annotation;

/**
 * 產品名稱的默認值提供者,無權限時顯示此值
 *
 * @author [email protected]
 * @date 2020/3/3
 */
public class ProductNameDefaultValueSupplier implements DefaultValueSupplier {

    @Override
    public Object supply() {
        return "默認的產品名稱";
    }

}


四、代碼地址

https://github.com/tangjizhou/tmall-backend/tree/dev/src/main/java/net/mshome/twisted/tmall/annotation

感謝此文作者:
https://www.cnblogs.com/lic309/p/5048631.html

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