I added security to my Jython/UI MVP framework. Full source code is available here. Current Google App Engine version is available here. Another version of an application (without security enabled) is available here. Also not Google App Engine (tested with Tomcat and Glassfish) has been created. Current documentation is available here.
Authentication and Shiro
Authentication is implemented using Apache Shiro. Only standalone API is used now. I found this framework very useful - it fulfills what is needed and is very simple at the same time. In sample application the simplest authentication realm is used but the doors is open to implement more sophisticated authentication environment.
Authentication and two modes
It is controlled by Authenticate property in app.properties file (example).
- Authenticate=Y : Restricted mode, access to all pages requires authentication token. Cannot get access without being authenticated.
- Authenticate=N (default) : Not restricted mode. Access for not authenticated users to some pages is possible.
Login enforcement
Starting pages (specified by 'start' http query argument) enforcing login page are specified in app.properties file. Parameter 'Login' should contain list of starting page requiring login window. All starting pages not specified there are accessible without authentication (only if not restricted mode is enabled).
Authentication token
After successful login authentication token is returned to the client. From now on all access to the server should be performed by using this token. Any password (with except to the login page) is stored at the client side just making all stuff more secure.
Authentication token and server
Unfortunately, this solution does not come cheap. It is related to the fact that the purpose is to have server side stateless, just ready to run in cloud environment. So the solution cannot rely on Shiro session because next request can be executed in a fresh instance of JVM just having previous session invalidated. The problem is described here in Shiro documentation. So both user name and password should be stored at the server side after user authentication ready to authenticate again against the Shiro session. Credentials are stored in a cache and also in backing persistent storage in case of being removed from cache. Implementation of cache and persistent storage is different for Google App Engine and not Google App Engine implementation. The simple algorithm for caching credentials is common.
public class SecuritySessionStore implements ISecuritySessionCache { private final ISecuritySessionMemCache iMemCache; private final ISecuritySessionPersistent iPersistent; private static final Logger log = Logger .getLogger(SecuritySessionStore.class.getName()); @Inject public SecuritySessionStore(ISecuritySessionMemCache iMemCache, ISecuritySessionPersistent iPersistent) { this.iMemCache = iMemCache; this.iPersistent = iPersistent; } @Override public Object get(String key) throws InvalidClassException { Object val = iMemCache.get(key); if (val != null) return val; val = iPersistent.get(key); if (val == null) return null; // add value to cache again log.log(Level.FINE, LogMess.getMessN(ILogMess.PUTINCACHEAGAIN, key)); iMemCache.put(key, val); return val; } @Override public void put(String key, Object o) { iMemCache.put(key, o); iPersistent.put(key, o); } @Override public void remove(String key) { iMemCache.remove(key); iPersistent.remove(key); } }By virtue of Guice the proper implementation of ISecuritySessionMemCache and ISecuritySessionPersitent is injected.
Authorization and Jexl
Three types of authorization is used. User, role and permission. Sometimes authorization rule can be complicated (for instance: give access to the user identified by name or the user having the role). So I decided to user Jexl for creating boolean expression evaluating to True (is authorized) or False (is not authorized).
To create an expression three methods are defined: sec.u (current user is the user authorized), sec.r (current user has the role) and sec.p (current user has permission). Using this three methods even complicated expression can be created.
Example:
Only 'darkhelmet' user is authorized and all user belonging to 'shwartz' role. Jexl expression: sec.u('dearkhelmer) or sec.r('schwartz')
Two types of authorization
Static way, restrict access in the xml dialog definition. Two attributes : 'readonly' and 'hidden' can be enriched with security expression. If expression evaluates to True for current user then attribute is enabled otherwise is disabled. This attributes can be attached to fields in the form, columns in the list and buttons. If 'hidden' is enabled then simple the element is not visible and not accessible. If 'readonly' is enabled then the element is visible but cannot be modified or clicked (in case of button).
Dynamic way, from 'jython' code. Authorization can be also enforced in 'jython' code at the server side just allowing to apply any logic to authorization rule. Examples:
from guice import ServiceInjector def dialogaction(action,var) : iSec = ServiceInjector.constructSecurity() token = var["SECURITY_TOKEN"] ok = iSec.isAuthorized(token,"sec.u('darkhelmet')") var["OK"] = okTests
Both types of tests: GUI testing using Boa/Selenium framework (test cases) and Junit test cases have been created for security enhancement.
Future, next steps
It is the first version of security enhancement. Further development:
- 'Hidden'. Current version simple applies 'hidden' attribute to HTML element. It is not rendered but is visible in HTML code. Future version will contain 'superhidden' attribute. Such a field will not be included in the HTML code.
- Current version stored password at the server side in plain text. It should be encoded in real production code.
- More attributes covered with authorization.
Brak komentarzy:
Prześlij komentarz