Page tree
Skip to end of metadata
Go to start of metadata

Stream playback security

Using IStreamPlaybackSecurity

Write a stream security handler that implements isPlaybackAllowed() method of IStreamPlaybackSecurity. It is unclear if we could use this to authorize or it can merely be used to limit certain streams to scopes.

import org.red5.server.api.IScope; 
import org.red5.server.api.stream.IStreamPlaybackSecurity; 
 
public class NamePlaybackSecurity implements IStreamPlaybackSecurity { 
 
  public boolean isPlaybackAllowed(IScope scope, String name, int start, int length, boolean flushPlaylist) { 
      if (!name.startswith("liveStream")) { 
        return false; 
      } else { 
        return true; 
      } 
  }; 
}

Register the handler in the Red5 app, appStart() method

registerStreamPlaybackSecurity(new NamePlaybackSecurity());

In case of VoV, we need to modify the appStart() method of the matterhorn-engage-streaming Red5 app

@Override
public boolean appStart(IScope app) {
   super.appStart(app);
   log.info("engage streaming appStart");
   System.out.println("engage streaming appStart"); 
   appScope = app;
   return true;
}

Authenticate using setScope()

The method described above is not ideal for authenticate in Red5 paradigm. Using setScope() we can authenticate individual user against each scope.

public boolean authenticate(String username, String password) {
    // do your stuff
    Red5.getLocalConnection().getClient().setAttribute("Authenticated", true);
    return true;
}
 
public boolean setScope(String[] path) {
    IConnection conn = Red5.getConnectionLocal();
    IClient client = conn.getClient();
    IScope destinationScope = getDestinationScope(path); // your own method

    // Now see whether the client is allowed to connect to the destination scope.
    if (destinationScope.getAttribute("RequiresAuthentication") == true) {
        if (client.getAttribute("Authenticated") != true)
            return false;

    // We got this far, so we must be allowed to connect
    conn.connect(destinationScope);

    // Carry attributes from original client object to new client object
    IClient newClient = conn.getClient();
    newClient.setAttribute("Authenticated", client.getAttribute("Authenticated"));
}

Authenticate from ActionScript

Client side. We could pass login credentials (unsafe) or a token to swf file.

NetConnection.connect("rtmp://pawpaw.dlib.indiana.edu/matterhorn-engage/Tutorials/Rails", "pdinh", "mypassword"); 

Override method connect() or appConnect() in ApplicationAdapter

@Override
public synchronized boolean connect(IConnection conn, IScope scope, Object[] params) {
  if (!super.connect(conn, scope, params))
    return false;
  if (!scope.getContextPath().startsWith("Tutorials") && !scope.getContextPath().startsWith("Rails"))
    rejectClient("Requested server scope not recognized");
  if (!Accounts.authenticate(params[0], params[1]))
    rejectClient("Invalid login details");
  return true;
} 

Additional conditions could be used to limit a particular user to a scope. More information on Authentication in Red5.

Security plugin for Red5

Secure connections

Red5 supports pure RTMPS. See Red5 documentation and Gregoire's blog.

Stream manipulations

Passing parameters directly to RTMP stream

Having params in RTMP stream request can be helpful as Red5 could directly use the params to authenticate / authorize the request. Most likely we'll want to pass a one-time token string to the stream so Red5 could validate it before streaming content out.

rtmp://streamserver.com/app/mystream.flv?sig=0983l3h4o8u449082

Red5 does not support reading params natively, but one could use a CustomFilenameGenerator and implement the generateFilename() method to manually handle the request. Note that Red5 attempts to add a file extension at the end if there is none.

public String generateFilename(IScope scope, String name, String extension,
   GenerationType type) {
  /*
   This will manually convert the name string into two seperate parts, one for filename
   and other for params support. Plus a little side effect of extension appended after the name string which is
   resolved automatically.
   */
  log.debug("Filename (before): "+name);
  if (name.indexOf("?")!=-1) {
   String param=name.split("\\?")[1];
   name=name.split("\\?")[0];
   if (name.indexOf(".")!=-1&&(name.length()-name.lastIndexOf("."))==4&&param.indexOf(".")!=-1) {
        String[] extensions={"flv","mp3","mp4","mpa","3gp","f4v","f4a","m4a","aac"};
        boolean hasExtension=false;
        for (int i=0;i<extensions.length;i++) {
        if (extensions[i].equalsIgnoreCase(name.substring(name.lastIndexOf(".")+1))) {
        i=extensions.length;
        hasExtension=true;
        }
        }
        if (!hasExtension) {
        name=name+param.substring(param.indexOf("."));
        }
   }
   if (name.indexOf(".")==-1&&param.indexOf(".")!=-1) {
        name=name+param.substring(param.indexOf("."));
   }
   if (param.indexOf(".")!=-1) {
        param=param.substring(0, param.indexOf("."));
   }
   log.debug("Filename (after): "+name);
   log.debug("Param (after): "+param);
  } else {
   //This will auto reject any stream connection that does not have params, you can modify this section as well.
   name=null;
   Red5.getConnectionLocal().getClient().disconnect();
   return null;
  }
  String filename;
  boolean permit= false;
  //MODIFYING BEGINS
  //This is where you may want to do such actions to actual change the path check up and such.
  //Also all the params are in "param" string.  It is not converted into array, this is something yourself can do.
  if (type == GenerationType.RECORD) {
   filename = recordPath + name;
  } else {
   filename = playbackPath + name;
  }
  //MODIFYING ENDS
  // Add extension if doesn't have extension included to the filename.
  if (extension != null) filename += extension;
  log.debug("Type: "+type+" Searching for: "+filename);
  if (permit==false) {
   name=null;
   Red5.getConnectionLocal().getClient().disconnect();
   return null;
  }
  return filename;
 }

Add the generator to /opt/matterhorn/contrib/matterhorn-engage-streaming/www/WEB-INF/red5-web.xml

<bean id="streamFilenameGenerator" class="path.to.your.CustomFilenameGenerator" /> 

Rebuild the app.

  • No labels