Generating One Time PIN/Password (OTP) and send them to end users using SMS/Text messages, as well as verifying the entered code. This enables 2-factor authentication.
The SMS Authentication plugin exists in 2 variants;
This is the configuration for dk.itp.security.authentication.sms.SMSAuthenticationPlugin which is handles all the details.
Property | Value | Description |
---|---|---|
sms.httpProxyHost | <hostname> Default: blank | Hostname of HTTP proxy to use when contacting SMS Gateway, leave blank to skip proxy. |
sms.httpProxyPort | <tcp port> Default: 8080 | TCP port number for proxy server. |
sms.httpProxyUser | <userid> Default: blank | If proxy is enabled, this is the userid to send, if specified |
sms.httpProxyPassword | <password> Default: blank | If proxy is enabled, and userid is specified, this is the password to send to the proxy server. |
sms.httpNoProxyFor | <pattern> No default | Hostnames/IPs matching this pattern will not be proxied |
sms.from | <string> Default: PP OTP | Name or phone number that the SMS/Text message will appear to be sent from. |
sms.otplength | <integer> Default: 6 | Number of characters in the generated OTP code |
sms.numbersonly | true/false Default: true | If true, generated OTP only contains numbers - if false, it contains letters too. |
sms.flashsms | true/false Default: true | If true, the SMS/Text message is sent as flash SMS - meaning it will popup on the phone, and disappear when the user dismisses it - it will also not show up in the SMS history. |
sms.verifysslhostname | true/false Default: true | Set to false to disable SSL hostname verification - when true, it checks that the hostname matches what is specified in the SSL server certificate. |
sms.verifysslservercert | true/false Default: true | Set to false to disable verification of the SSL server certificate |
sms.provider | cpssm/unwire | Name of the SMS provider to use, must be cpsms or unwire An additional value of "locallogging" can be set to provide the OTP codes to the Ceptor log files. |
When SMS provider is set to cpsms | ||
sms.server | URL Default: https://www.cpsms.dk | URL (scheme and hostname) of the SMS gateway server |
sms.username | <userid> | Userid to send to the SMS gateway |
sms.password | <password> | Password for the SMS gateway |
sms.apikey | <string> | API Key to use instead of the userid/password when calling the gateway. |
When SMS provider is set to unwire | ||
sms.server | URL Default: https://gw.unwire.com | URL (scheme and hostname) of the SMS gateway server |
sms.username | <userid> | Userid to send to the SMS gateway |
sms.password | <password> | Password for the SMS gateway |
sms.smsc | <string> Default: dk.tdc | Company to route the SMS through Refer to unwire for details. |
sms.appnr | <number> Default: 1231 | Number within the routing company to use for sending the SMS Refer to unwire for details. |
sms.mediacode | <string> Default: PortalProtectOTP | Mediacode to use Refer to unwire for details. |
When using the version of the SMS authentication plugin that uses the useradmin database; dk.itp.portalprotect.useradmin.server.SMSUAAuthenticationPlugin the following additional configuration properties exist:
Property | Value | Description |
---|---|---|
useradminservers | <url> Default: localhost:15000 | URL to useradmin server |
ua_userid | <userid> | Userid to use when authenticating to useradmin server |
ua_password | <password> | Password to use when authenticating to useradmin server |
useridpassword.autounlockminutes | <value in minutes> Default: 0 | If nonzero, and user was automatically locked due to too many failed password attempts, he will automatically be unlocked after the specified number of minutes. |
useridpassword.maximuminvalidpasswordattempts | <number> Default: 0 | If nonzero, and if invalid login attempts reaches this limit, the user is automatically locked. |
This is an example of an implementation of your own authentication plugin, which inherits from the abstract one.
Please note that this example just covers
package dk.portalprotect.nodb.plugins; import java.io.IOException; import java.net.MalformedURLException; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import dk.itp.security.authentication.sms.SMSAuthenticationPlugin; import dk.itp.security.passticket.PTException; import dk.itp.security.passticket.User; import dk.itp.security.passticket.server.AuthErrorCodes; /** * Examplle authentication plugin, used to demonstrate authenticating using SMS. * * @author Kim Rasmussen * * <pre> * Ceptor - http://ceptor.io * * This source code is confidential. * </pre> */ public class SMSAuthenticationExample extends SMSAuthenticationPlugin { static Logger log = LoggerFactory.getLogger(SMSAuthenticationExample.class.getName()); static final int DEFAULT_AUTHLEVEL = 10; /** * @see dk.itp.security.authentication.IAuthenticationPlugin#getAuthenticationLevel() */ public int getAuthenticationLevel() { return DEFAULT_AUTHLEVEL; } /** * @see dk.itp.security.authentication.IAuthenticationPlugin#login(User, String, Object, Object) */ public void login(User user, String userid, Object credentials) throws PTException { if (credentials instanceof String) { user.removeStateObject("SMS_OTP"); user.removeStateObject("SMS_USERID"); // Lookup userid in user repository, verify password // In this NON-PRODUCTION example, we assume the userid is the phone number, and the password is "password" if (!credentials.equals("password")) { throw new PTException("Invalid password", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid credentials"); } // If we get here, the password is ok // For now, use the userid as phone number - in a real authentication plugin, look it up somewhere else. String mobile = userid; mobile = mobile == null ? "" : mobile.trim(); if (mobile.startsWith("+")) mobile = mobile.substring(1); int ofs = mobile.indexOf(' '); while(ofs >= 0) { mobile = mobile.substring(0,ofs) + mobile.substring(ofs+1); ofs = mobile.indexOf(' '); } if (mobile == null || mobile.length() == 0) throw new PTException("No mobile number on record, cannot send PIN via SMS", AuthErrorCodes.ERROR_NOMOBILEPHONE, "No mobile number, cannot send SMS"); // If 8 chars, assume danish and add country code 45 for Denmark if (mobile.length() == 8) mobile = "45"+mobile; // Generate an OTP, store it in the session, and throw an exception telling the application we need the OTP. String otp = generateOTP(); user.setStateObject("SMS_OTP", otp); user.setStateObject("SMS_USERID", userid); try { if (!sendSMS(mobile, "PIN:\n"+otp)) throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Problem with mobile, cannot send SMS"); } catch (MalformedURLException e) { throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Configuration problem - invalid SMS configuration", e); } catch (IOException e) { throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Problem contacting SMS Gateway", e); } log.info("Logon with userid/password OK - SMS sent to " + mobile + ", need relogin with pincode"); throw new PTException("Need OTP pincode", AuthErrorCodes.ERROR_NEED_OTP, "Need OTP code"); } else if (credentials instanceof String[]) { // String[] means we are called with the one-time password. userid = (String) user.getStateObject("SMS_USERID"); String otp = (String)user.getStateObject("SMS_OTP"); if (userid == null || otp == null) { throw new PTException("Problem logging in, no userid present or timeout", AuthErrorCodes.ERROR_GENERALERROR, "No previous userid/password login or timeout"); } // Remove allows only one try - keep it to allow multiple attempts at entering the correct PIN user.removeStateObject("SMS_OTP"); user.removeStateObject("SMS_USERID"); if (!((String[])credentials)[0].equalsIgnoreCase(otp)) { throw new PTException("Invalid OTP PIN", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid PIN code"); } user.userid = userid; user.isLoggedOn = true; // Fill remaining information onto session } else { log.error("Cannot login, unsupported type of credentials"); throw new PTException("Unsupported type of credentials - cannot login", AuthErrorCodes.ERROR_GENERALERROR, "Unsupported type of credentials"); } } /** * @see dk.itp.security.authentication.IAuthenticationPlugin#setConfiguration(Properties) */ public void setConfiguration(Properties props) { super.setConfiguration(props); log.error("Using Example SMS Plugin with hardcoded credentials - NOT FOR PRODUCTION"); } } |