Created
June 28, 2020 16:15
-
-
Save MuellerConstantin/fab24b7c595488fcd0c6c73f668a139a to your computer and use it in GitHub Desktop.
JSON and SpEL based "Attribute Based Access Control (ABAC)" for the Spring Framework
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.security.access.PermissionEvaluator; | |
import org.springframework.security.core.Authentication; | |
import javax.persistence.EntityManager; | |
import javax.persistence.EntityNotFoundException; | |
import java.io.Serializable; | |
import java.time.LocalDateTime; | |
import java.util.HashMap; | |
import java.util.Map; | |
public class AbacPermissionEvaluator implements PermissionEvaluator { | |
private EntityManager entityManager; | |
private PolicyEnforcement policyEnforcement; | |
public AbacPermissionEvaluator(EntityManager entityManager, PolicyEnforcement policyEnforcement) { | |
this.entityManager = entityManager; | |
this.policyEnforcement = policyEnforcement; | |
} | |
@Override | |
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { | |
Map<String, Object> environment = new HashMap<>(); | |
environment.put("time", LocalDateTime.now()); | |
Object principal = authentication.getPrincipal(); | |
return policyEnforcement.evaluate(principal, targetDomainObject, permission, environment); | |
} | |
@Override | |
public boolean hasPermission(Authentication authentication, Serializable targetDomainId, String targetDomainType, Object permission) { | |
Object targetDomainObject = null; | |
try { | |
targetDomainObject = entityManager.find(Class.forName(targetDomainType), targetDomainId); | |
} catch (ClassNotFoundException exc) { | |
throw new RuntimeException(exc); | |
} | |
if (null == targetDomainObject) { | |
throw new EntityNotFoundException(String.format("Can't find domain target of type %s with ID %s", targetDomainType, targetDomainId)); | |
} | |
Map<String, Object> environment = new HashMap<>(); | |
environment.put("time", LocalDateTime.now()); | |
Object principal = authentication.getPrincipal(); | |
return policyEnforcement.evaluate(principal, targetDomainObject, permission, environment); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import java.util.stream.Collectors; | |
public class DefaultPolicyEnforcement implements PolicyEnforcement { | |
private PolicyDefinition policyDefinition; | |
public DefaultPolicyEnforcement(PolicyDefinition policyDefinition) { | |
this.policyDefinition = policyDefinition; | |
} | |
@Override | |
public boolean evaluate(Object subject, Object resource, Object action, Object environment) { | |
List<PolicyRule> policyRules = policyDefinition.getRules(); | |
SecurityAbacContext context = new SecurityAbacContext(subject, resource, action, environment); | |
List<PolicyRule> matchedRules = filterRules(policyRules, context); | |
return evaluateRules(matchedRules, context); | |
} | |
private List<PolicyRule> filterRules(List<PolicyRule> policyRules, SecurityAbacContext context) { | |
return policyRules.stream() | |
.filter(policyRule -> policyRule.getTarget().getValue(context, Boolean.class)) | |
.collect(Collectors.toList()); | |
} | |
private boolean evaluateRules(List<PolicyRule> matchedRules, SecurityAbacContext context) { | |
return matchedRules.stream() | |
.anyMatch(policyRule -> policyRule.getCondition().getValue(context, Boolean.class)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.fasterxml.jackson.databind.JsonMappingException; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import org.springframework.beans.factory.InitializingBean; | |
import org.springframework.core.io.Resource; | |
import org.x1c1b.pastefly.security.abac.PolicyDefinition; | |
import org.x1c1b.pastefly.security.abac.PolicyRule; | |
import java.io.IOException; | |
import java.util.List; | |
public class JsonPolicyDefinition implements PolicyDefinition, InitializingBean { | |
private final Logger log = LoggerFactory.getLogger(LoggingController.class); | |
private Resource policyResource; | |
private ObjectMapper objectMapper; | |
private PolicyRule[] policyRules; | |
public JsonPolicyDefinition(Resource policyResource, ObjectMapper objectMapper) { | |
this.policyResource = policyResource; | |
this.objectMapper = objectMapper; | |
} | |
@Override | |
public List<PolicyRule> getRules() { | |
return List.of(policyRules); | |
} | |
@Override | |
public void afterPropertiesSet() { | |
try { | |
if (null == policyResource || !policyResource.exists()) { | |
log.warn("Policy file is missing"); | |
return; | |
} | |
log.debug("Loading policy file at: {}", policyResource.getURI()); | |
policyRules = objectMapper.readValue(policyResource.getInputStream(), PolicyRule[].class); | |
log.info("Policy loaded successfully"); | |
} catch (JsonMappingException exc) { | |
log.error("An error occurred while parsing the policy file", exc); | |
} catch (IOException exc) { | |
log.error("Failed to read policy file", exc); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
public interface PolicyDefinition { | |
List<PolicyRule> getRules(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface PolicyEnforcement { | |
boolean evaluate(Object subject, Object resource, Object action, Object environment); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | |
import org.springframework.expression.Expression; | |
import org.x1c1b.pastefly.security.abac.json.SpelDeserializer; | |
public class PolicyRule { | |
private String description; | |
@JsonDeserialize(using = SpelDeserializer.class) | |
private Expression target; | |
@JsonDeserialize(using = SpelDeserializer.class) | |
private Expression condition; | |
public String getDescription() { | |
return description; | |
} | |
public void setDescription(String description) { | |
this.description = description; | |
} | |
public Expression getTarget() { | |
return target; | |
} | |
public void setTarget(Expression target) { | |
this.target = target; | |
} | |
public Expression getCondition() { | |
return condition; | |
} | |
public void setCondition(Expression condition) { | |
this.condition = condition; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SecurityAbacContext { | |
private Object subject; | |
private Object resource; | |
private Object action; | |
private Object environment; | |
public Object getSubject() { | |
return subject; | |
} | |
public void setSubject(Object subject) { | |
this.subject = subject; | |
} | |
public Object getResource() { | |
return resource; | |
} | |
public void setResource(Object resource) { | |
this.resource = resource; | |
} | |
public Object getAction() { | |
return action; | |
} | |
public void setAction(Object action) { | |
this.action = action; | |
} | |
public Object getEnvironment() { | |
return environment; | |
} | |
public void setEnvironment(Object environment) { | |
this.environment = environment; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.fasterxml.jackson.core.JsonParser; | |
import com.fasterxml.jackson.databind.DeserializationContext; | |
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; | |
import org.springframework.expression.Expression; | |
import org.springframework.expression.ExpressionParser; | |
import org.springframework.expression.spel.standard.SpelExpressionParser; | |
import java.io.IOException; | |
public class SpelDeserializer extends StdDeserializer<Expression> { | |
private ExpressionParser expressionParser; | |
public SpelDeserializer(ExpressionParser expressionParser) { | |
super(Expression.class); | |
this.expressionParser = expressionParser; | |
} | |
public SpelDeserializer() { | |
this(new SpelExpressionParser()); | |
} | |
@Override | |
public Expression deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { | |
String expression = jsonParser.getCodec().readValue(jsonParser, String.class); | |
return expressionParser.parseExpression(expression); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment