Please for the love of God do not create a SecurityUtils
class!
Your class will become 10k lines of spaghetti code in a matter of months! You would need to have an Action
type (create, read, update, destroy, list, etc.) passed into your isUserAuthorized()
method, which would quickly become a thousand-line-long switch
statement with increasingly complex logic that would be difficult to unit test. Don't do it.
Generally what I do, at least in Ruby on Rails, is have each domain object be responsible for its own access privileges by having a policy class for each model. Then, the controller asks the policy class if the current user for the request has access to the resource or not. Here's an example in Ruby, since I've never implemented anything like it in Java, but the idea should come across cleanly:
class OrderPolicy
class Scope < Struct.new(:user, :scope)
def resolve
# A user must be logged in to interact with this resource at all
raise NotAuthorizedException unless user
# Admin/moderator can see all orders
if (user.admin? || user.moderator?)
scope.all
else
# Only allow the user to see their own orders
scope.where(orderer_id: user.id)
end
end
end
# Constructor, if you don't know Ruby
def initialize(user, order)
raise NotAuthorizedException unless user
@user = user
@order= order
end
# Whitelist what data can be manipulated by each type of user
def valid_attributes
if @user.admin?
[:probably, :want, :to, :let, :admin, :update, :everything]
elsif @user.moderator?
[:fewer, :attributes, :but, :still, :most]
else
[:regualar, :user, :attributes]
end
end
# Maybe restrict updatable attributes further
def valid_update_attributes
end
# Who can create new orders
def create?
true # anyone, and they would have been authenticated already by #initialize
end
# Read operation
def show?
@user.admin? || @user.moderator? || owns_order
end
# Only superusers can update resources
def update?
@user.admin? || @user.moderator?
end
# Only admins can delete, because it's extremely destructive or whatever
def destroy?
@user.admin?
end
private
# A user 'owns' an order if they were the person who submitted the order
# E.g. superusers can access the order, but they didn't create it
def owns_order
@order.orderer_id == @user.id
end
end
Even if you have complex nested resources, some resource must 'own' the nested resources, so the top-level logic inherently bubbles down. However, those nested resources need their own policy classes in the event that they can be updated independently of the 'parent' resource.
In my application, which is for my university's department, everything revolves around the Course
object. It's not a User
-centric application. However, User
s are enrolled in Course
, so I can simply ensure that:
@course.users.include? current_user && (whatever_other_logic_I_need)
for any resource that a particular User
needs to modify, since almost all resources are tied to a Course
. This is done in the policy class in the owns_whatever
method.
I haven't done much Java architecture, but it seems that you could make a Policy
interface, where the different resources that need to be authenticated must implement the interface. Then, you have all the necessary methods which can become as complex as you need them to be per domain object. The important thing is to tie the logic to the model itself, but at the same time keep it in a separate class (single responsibility principle (SRP)).
Your controller actions could look something like:
public List<Order> index(OrderQuery query) {
authorize(Order.class)
// you should be auto-rescuing the NotAuthorizedException thrown by
//the policy class at the controller level (or application level)
// if the authorization didn't fail/rescue from exception, just render the resource
List<Order> orders = db.search(query);
return renderJSON(orders);
}
public Order show(int orderId) {
authorize(Order.class)
Order order = db.find(orderId);
return renderJSON(order);
}
Best Answer
As opposed to serverless, you might be thinking of a microserver architecture.
In either case, you break up the API into smaller pieces. For example have a micro server handle all authentication and authorization requests. So if you have another microserver that returns a list of items specific to a user, you need to call the authorization micro server from the list micro server. Lambas would work in a similar manner.
You could use Django for each microserver. It is also common to use Flask as well. You will need a separate data store for each microserver. However, I am under the impression that Django is not typically used to implement Lambdas.
If you are considering going the microserver route, you might also consider a container service to develop/deploy your micro servers. Something based on Kubernetes like OpenShift.