Java JAAS form based authentication
JAASUserPrincipal.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.rainyday.server.login; import java.io.Serializable; import java.security.Principal; /** * @author semika * */ public class JAASUserPrincipal implements Principal, Serializable { private String name; /** * @param name */ public JAASUserPrincipal(String name) { if (name == null ) { throw new NullPointerException( "NULL user name" ); } this .name = name; } @Override public String getName() { return name; } @Override public String toString() { return "UserPrincipal [name=" + name + "]" ; } @Override public int hashCode() { final int prime = 31 ; int result = 1 ; result = prime * result + ((name == null ) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if ( this == obj) return true ; if (obj == null ) return false ; if (getClass() != obj.getClass()) return false ; JAASUserPrincipal other = (JAASUserPrincipal) obj; if (name == null ) { if (other.name != null ) return false ; } else if (!name.equals(other.name)) return false ; return true ; } } |
JAASRolePrincipal.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package com.rainyday.server.login; import java.io.Serializable; import java.security.Principal; /** * @author semika * */ public class JAASRolePrincipal implements Principal, Serializable { private String name; /** * @param name */ public JAASRolePrincipal(String name) { if (name == null ) { throw new NullPointerException( "NULL role name" ); } this .name = name; } @Override public String getName() { return name; } @Override public String toString() { return "JASSRolePrincipal [name=" + name + "]" ; } @Override public int hashCode() { final int prime = 31 ; int result = 1 ; result = prime * result + ((name == null ) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if ( this == obj) return true ; if (obj == null ) return false ; if (getClass() != obj.getClass()) return false ; JAASRolePrincipal other = (JAASRolePrincipal) obj; if (name == null ) { if (other.name != null ) return false ; } else if (!name.equals(other.name)) return false ; return true ; } } |
JAASPasswordPrincipal.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.rainyday.server.login; import java.io.Serializable; import java.security.Principal; /** * @author semika * */ public class JAASPasswordPrincipal implements Principal, Serializable { private String name; /** * @param name */ public JAASPasswordPrincipal(String name) { if (name == null ) { throw new NullPointerException( "NULL password." ); } this .name = name; } @Override public String getName() { return name; } @Override public int hashCode() { final int prime = 31 ; int result = 1 ; result = prime * result + ((name == null ) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if ( this == obj) return true ; if (obj == null ) return false ; if (getClass() != obj.getClass()) return false ; JAASPasswordPrincipal other = (JAASPasswordPrincipal) obj; if (name == null ) { if (other.name != null ) return false ; } else if (!name.equals(other.name)) return false ; return true ; } @Override public String toString() { return "JAASPasswordPrincipal [name=" + name + "]" ; } } |
01 02 03 04 05 06 07 08 09 10 11 | rainyDay { com.rainyday.server.login.JAASLoginModule required dbDriver= "com.mysql.jdbc.Driver" dbURL= "jdbc:mysql://localhost/rainyday" dbUser= "root" dbPassword= "abc123" userQuery= "select username from secu_user where secu_user.username=? and secu_user.password=?" roleQuery= "select secu_user_role.rolename from secu_user, secu_user_role " + "where secu_user.username=secu_user_role.username and secu_user.username=?" debug= true ; }; |
‘jass.config’ file should have this similar format. In addition to the login module declaration, You can declare options as your wish. These options are made available by login module with ‘options’ map in initialize() method argument.
Additionally, We should tell the tomcat, Where to locate the ‘jaas.config’ file by adding it’s path to JAVA_OPTS environment variable. I have added this into ‘catalina.sh’ file under $CATALINA_HOME/bin as follows.
JAVA_OPTS=”$JAVA_OPTS -Djava.security.auth.login.config==../conf/jaas.config”
Next, You need to declare the JAASRealm configurations. You can add a new ‘Realm’ entry into the server.xml file under $CATALINA_HOME/conf. In our tutorial, the ‘Realm’ entry is as follows.
1 2 3 4 | <Realm className= "org.apache.catalina.realm.JAASRealm" appName= "rainyDay" userClassNames= "com.rainyday.server.login.JASSUserPrincipal,com.rainyday.server.login.JAASPasswordPrincipal" roleClassNames= "com.rainyday.server.login.JASSRolePrincipal" /> |
For apache tomcat’s realm configuration, You can view this documentation. The complete source code for our jaas login module.
JAASLoginModule.java
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | package com.rainyday.server.login; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import org.apache.log4j.Logger; /** * @author semika * */ public class JAASLoginModule implements LoginModule { private static Logger LOGGER = Logger.getLogger(JAASLoginModule. class ); // initial state private Subject subject; private CallbackHandler callbackHandler; private Map sharedState; private Map options; // configurable option private boolean debug = false ; // the authentication status private boolean succeeded = false ; private boolean commitSucceeded = false ; //user credentials private String username = null ; private char [] password = null ; //user principle private JAASUserPrincipal userPrincipal = null ; private JAASPasswordPrincipal passwordPrincipal = null ; public JAASLoginModule() { super (); } @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<string, ?= "" > sharedState, Map<string, ?= "" > options) { this .subject = subject; this .callbackHandler = callbackHandler; this .sharedState = sharedState; this .options = options; debug = "true" .equalsIgnoreCase((String)options.get( "debug" )); } @Override public boolean login() throws LoginException { if (callbackHandler == null ){ throw new LoginException( "Error: no CallbackHandler available " + "to garner authentication information from the user" ); } Callback[] callbacks = new Callback[ 2 ]; callbacks[ 0 ] = new NameCallback( "username" ); callbacks[ 1 ] = new PasswordCallback( "password: " , false ); try { callbackHandler.handle(callbacks); username = ((NameCallback)callbacks[ 0 ]).getName(); password = ((PasswordCallback)callbacks[ 1 ]).getPassword(); if (debug) { LOGGER.debug( "Username :" + username); LOGGER.debug( "Password : " + password); } if (username == null || password == null ) { LOGGER.error( "Callback handler does not return login data properly" ); throw new LoginException( "Callback handler does not return login data properly" ); } if (isValidUser()) { //validate user. succeeded = true ; return true ; } } catch (IOException e) { e.printStackTrace(); } catch (UnsupportedCallbackException e) { e.printStackTrace(); } return false ; } @Override public boolean commit() throws LoginException { if (succeeded == false ) { return false ; } else { userPrincipal = new JAASUserPrincipal(username); if (!subject.getPrincipals().contains(userPrincipal)) { subject.getPrincipals().add(userPrincipal); LOGGER.debug( "User principal added:" + userPrincipal); } passwordPrincipal = new JAASPasswordPrincipal( new String(password)); if (!subject.getPrincipals().contains(passwordPrincipal)) { subject.getPrincipals().add(passwordPrincipal); LOGGER.debug( "Password principal added: " + passwordPrincipal); } //populate subject with roles. List<string> roles = getRoles(); for (String role: roles) { JAASRolePrincipal rolePrincipal = new JAASRolePrincipal(role); if (!subject.getPrincipals().contains(rolePrincipal)) { subject.getPrincipals().add(rolePrincipal); LOGGER.debug( "Role principal added: " + rolePrincipal); } } commitSucceeded = true ; LOGGER.info( "Login subject were successfully populated with principals and roles" ); return true ; } } @Override public boolean abort() throws LoginException { if (succeeded == false ) { return false ; } else if (succeeded == true && commitSucceeded == false ) { succeeded = false ; username = null ; if (password != null ) { password = null ; } userPrincipal = null ; } else { logout(); } return true ; } @Override public boolean logout() throws LoginException { subject.getPrincipals().remove(userPrincipal); succeeded = false ; succeeded = commitSucceeded; username = null ; if (password != null ) { for ( int i = 0 ; i < password.length; i++){ password[i] = ' ' ; password = null ; } } userPrincipal = null ; return true ; } private boolean isValidUser() throws LoginException { String sql = (String)options.get( "userQuery" ); Connection con = null ; ResultSet rs = null ; PreparedStatement stmt = null ; try { con = getConnection(); stmt = con.prepareStatement(sql); stmt.setString( 1 , username); stmt.setString( 2 , new String(password)); rs = stmt.executeQuery(); if (rs.next()) { //User exist with the given user name and password. return true ; } } catch (Exception e) { LOGGER.error( "Error when loading user from the database " + e); e.printStackTrace(); } finally { try { rs.close(); } catch (SQLException e) { LOGGER.error( "Error when closing result set." + e); } try { stmt.close(); } catch (SQLException e) { LOGGER.error( "Error when closing statement." + e); } try { con.close(); } catch (SQLException e) { LOGGER.error( "Error when closing connection." + e); } } return false ; } /** * Returns list of roles assigned to authenticated user. * @return */ private List<string> getRoles() { Connection con = null ; ResultSet rs = null ; PreparedStatement stmt = null ; List<string> roleList = new ArrayList<string>(); try { con = getConnection(); String sql = (String)options.get( "roleQuery" ); stmt = con.prepareStatement(sql); stmt.setString( 1 , username); rs = stmt.executeQuery(); if (rs.next()) { roleList.add(rs.getString( "rolename" )); } } catch (Exception e) { LOGGER.error( "Error when loading user from the database " + e); e.printStackTrace(); } finally { try { rs.close(); } catch (SQLException e) { LOGGER.error( "Error when closing result set." + e); } try { stmt.close(); } catch (SQLException e) { LOGGER.error( "Error when closing statement." + e); } try { con.close(); } catch (SQLException e) { LOGGER.error( "Error when closing connection." + e); } } return roleList; } /** * Returns JDBC connection * @return * @throws LoginException */ private Connection getConnection() throws LoginException { String dBUser = (String)options.get( "dbUser" ); String dBPassword = (String)options.get( "dbPassword" ); String dBUrl = (String)options.get( "dbURL" ); String dBDriver = (String)options.get( "dbDriver" ); Connection con = null ; try { //loading driver Class.forName (dBDriver).newInstance(); con = DriverManager.getConnection (dBUrl, dBUser, dBPassword); } catch (Exception e) { LOGGER.error( "Error when creating database connection" + e); e.printStackTrace(); } finally { } return con; } } |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | < login-config > < auth-method >FORM</ auth-method > < realm-name >rainyDay</ realm-name > < form-login-config > < form-login-page >/login.jsp</ form-login-page > < form-error-page >/error.jsp</ form-error-page > </ form-login-config > </ login-config > < security-role > < role-name >*</ role-name > </ security-role > < security-constraint > < web-resource-collection > < web-resource-name >Rainy day</ web-resource-name > < url-pattern >/</ url-pattern > < http-method >POST</ http-method > < http-method >GET</ http-method > </ web-resource-collection > < auth-constraint > < role-name >*</ role-name > </ auth-constraint > </ security-constraint > |
With the above security constraints, if some request comes to a particular resource in the protected area of the application with out the authentication, the request will be redirected to the ‘login’ page. Next, I will show you the simple HTML form which invokes our login module with the submission of the form.
1 2 3 4 5 | <form id= "loginForm" name= "loginForm" method= "post" action= "j_security_check" > User Name : <input id= "username" type= "text" name= "j_username" class = "textbox" ></input> Password : <input id= "password" type= "password" name= "j_password" class = "textbox" ></input> <input name= "login" type= "submit" value= "LOGIN" id= "submit" class = "button blue" > </form> |
JAASCallbackHandler.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package com.rainyday.server.login; import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import org.apache.log4j.Logger; /** * @author semika * */ public class JAASCallbackHandler implements CallbackHandler { private static final Logger LOGGER = Logger.getLogger(JAASCallbackHandler. class ); private String username = null ; private String password = null ; /** * @param username * @param password */ public JAASCallbackHandler(String username, String password) { this .username = username; this .password = password; } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { LOGGER.info( "Callback Handler invoked " ); for ( int i = 0 ; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callbacks[i]; nameCallback.setName(username); } else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback) callbacks[i]; passwordCallback.setPassword(password.toCharArray()); } else { throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported" ); } } } } |
Next, We have to create an instance of ‘ LoginContext’ to invoke the authentication explicitly.
01 02 03 04 05 06 07 08 09 10 11 12 | LoginContext lc = null ; try { lc = new LoginContext( "rainyDay" , new JAASCallbackHandler(username, password)); lc.login(); //get the subject. Subject subject = lc.getSubject(); //get principals subject.getPrincipals(); LOGGER.info( "established new logincontext" ); } catch (LoginException e) { LOGGER.error( "Authentication failed " + e); } |
Reference: Java form based authentication from our JCG partner Semika loku kaluge at the Code Box blog.
great job sir!
replace
if (rs.next()) {
}
by
while(rs.next()){
}
so all user roles get listed in roleList
I think the admin of this site is in fact working hard in support of his web page, as here every data is quality based information.
Hello Semika and thank your for your tuto.
Just one question about adding to your login module, a password expiration mechanism.
I imagine it can be done by adding an extra-column last_time_password_changed (timestamp) to the secu_user and add a new jsp page changepassword.jsp.
So how would you modify your login module to handle this? How would you redirect user to changepassword.jsp pas, from the login module, if he logged-in but his password has expired?
Thanks
I became your fan.nice work
Hi Semika,
Interesting example. I was trying something along the same lines.
I am using Spring as well. Is there any example you have tried which does the db properties injection using spring.
thanks
I am having issue with jetty8 for setting request.setUserPrinciple as _NOBODY which was allowed in jetty version 6
request.setUserPrinciple(_NOBODY)
is there any other way to do the same with jetty8 Basically the requirement here is bypass authentication for certain requests in JAAS realmAuthentication
Hi Semika,
I am trying to run this example at Wildfly8.2 AS with user.properties & role.properties. JAASLoginModule I have configure into standanlone.xml and jboss-web.xml
standalone.xml :-
jboss-web.xml :-
rainyDay
It works thank you,
was awsoem
LoginContext lc = null;
try {
lc = new LoginContext(“rainyDay”, new JAASCallbackHandler(username, password));
lc.login();
//get the subject.
Subject subject = lc.getSubject();
//get principals
subject.getPrincipals();
LOGGER.info(“established new logincontext”);
} catch (LoginException e) {
LOGGER.error(“Authentication failed ” + e);
}
Where to place the code of creating an instance?
can you please tell me where to add this file?
LoginContext lc = null;
try {
lc = new LoginContext(“rainyDay”, new JAASCallbackHandler(username, password));
lc.login();
//get the subject.
Subject subject = lc.getSubject();
//get principals
subject.getPrincipals();
LOGGER.info(“established new logincontext”);
} catch (LoginException e) {
LOGGER.error(“Authentication failed ” + e);
}