Whenever a method request comes into the server, JSecurity must be able to work out which Subject (person) the request is for, inorder to calculate if that Subject is authorized to access the method being called.
Typically JSecurity stores the Subject in a ThreadLocal because, typically, a single Thread is used to service a single client session. Whenever a new request comes in, therefore, JSecurity can just whip the Subject back out of the Thread executing the method and it knows who it’s dealing with.
Wowza, however, assigns a seperate Thread for every client interaction from a Thread Pool. This breaks the JSecurity scheme in two ways, as between method requests not only is the Subject unlikely to be available (unless by chance the same Thread is picked from the ThreadPool a second time), but the wrong subject from a previous method call may still be attached throwing the whole thing into chaos.
Bridging the gap between the Thread Pool and the Session
Ideally we want the JSecurity framework to function without any changes to it’s code, as changes introduce instability
As JSecurity assumes that it can access the appropriate subject in Thread Local, this is what we have to achieve.
The basic idea is to store and retrieve a Subject relative to a Wowza Client… so that before any method requiring JSecurity is called, we can recover the appropriate Subject and place it on the currently executing local Thread so that to JSecurity it is as if we never changed threads at all.
Wowza provides an IClient object with every incoming method call, where client.getClientId() returns a unique id. We therefore store our Subjects in a HashMap where the key is Wowza’s clientId. Here is the WowzaSubjectContainer (WSC) which will hold our Subjects between Threads/Method calls :
public class WowzaSubjectContainer {
HashMap wsc = new HashMap();
public void setSubject(int id, Subject subject) {
wsc.put(id, subject);
}
public Subject getSubject(int id) {
return wsc.get(id);
}
public Boolean hasSubjectKey(int id){
return wsc.containsKey(id);
}
public Boolean hasSubjectObj(Subject subject){
return wsc.containsValue(subject);
}
public Integer findSubjectKey(Subject subject) {
Set keyset = wsc.keySet();
Integer found = -1;
for (Integer key : keyset) {
if (wsc.get(key).equals(subject)){
found = key;
}
}
return found;
}
public void delSubjectObj(int clientId) {
wsc.remove(clientId);
}
}
Before any methods using JSecurity are called we therefore have to associate the Subject with the current thread, and at the end of that thread’s execution we need to clean up and remove it. If the Subject has been logged out in the meantime, we also must remove them from our WSC :
/***
* Assumption:
* This method is called before getSubject() or unbind() is called in JSecurity by annotations or otherwise.
*
* Strategy:
* If a Subject already exists on the ThreadLocal it is left over over from an earlier interaction
* (i.e. an earlier Thread chosen again from the ThreadPool) and needs to be removed. Generally, this should not happen,
* the Subject should already have been removed by disassociateSubjectWithThread.
*
* If we have stored a Subject with a matching clientId() in our WSC, then recover it and put it on the ThreadLocal.
* @param clientId
*/
private void associateSubjectWithThread(int clientId) {
//Remove any leftover Subject hanging about from the ThreadPool
if (ThreadContext.getSubject()!=null){
ThreadContext.unbindSubject();
}
//If we have "persisted" a subject associated with this clientId, place it on the local Thread
if (wsc.hasSubjectKey(clientId)){
ThreadContext.bind(wsc.getSubject(clientId));
}
}
And for disassociation :
/***
* Strategy :
* If there is no longer a subject on ThreadLocal, then it has been unbound (logged out),
* so it must be removed from our WSC.
*
* If there *is* a subject on ThreadLocal lets persist it in WSC and then remove it from the thread.
* @param clientId
*/
private void disassociateSubjectWithThread(int clientId) {
Subject s = ThreadContext.getSubject();
//If there's no Subject on the local Thread
if (s==null){
wsc.delSubjectObj(clientId);
}
//If there IS a subject on the local thread, lets persist it (quicker to overwrite than to check for prior existance)
wsc.setSubject(clientId, s);
//then tidy it up from the local thread
ThreadContext.unbindSubject();
}
Now, wherever we have a method call in our Wowza ModuleBase we need to call these functions before any further JSecurity protected methods will be called e.g.
public void protectedMethod(IClient client,RequestFunction function,AMFDataList params){
associateSubjectWithThread(client.getClientId());
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated() ) {
getLogger().info("Calling protected method");
mso.SecureMethodForAdmin();
sendResult(client, params, "Called protected method");
}
disassociateSubjectWithThread(client.getClientId());
}
Of course, topping and tailing methods with these (dis)associate calls is a pain… so it’s probably worth using AOP and creating an Annotation.
Update 1 :
The ever helpful Charlie from the Wowza team points out that I could store the Subject in the client.get/setProperties() container. This would mean that on every method call I simply grab the subject from client.getProperties() and write it to the localThread. At the end of the call I put it back.
This is much better, but now it’s become more important to try to find the right way not to have to call the (dia)associate methods at the beginning/end of each method call. Maybe there’s a way without AOP? There will be a followup post soon (I hope).