需求描述:
傳統項目中,經常會遇到系統根據用戶權限的不同,前臺部分數據需要打碼或者直接隱藏。而且後臺返回的數據也需要模糊處理。
文章目錄
一、思路和要求
思路: 後端會經過序列化然後傳輸到前臺,我們需要在序列化的時候針對這些需要打碼的字段進行特殊處理。
要求: 實現需要有通用性,且最好能做到所有有權限控制的字段
在序列化時都會經過我們的權限判斷進行統一
處理。
二、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 "默認的產品名稱";
}
}