how to create custom JDBCrealm? - java

i'm trying to secure my enterprise web-app!
i have to constraint resources.
Since i have all stored in my db (users and roles), i won't create a fileRealm or store any user's credential in (Glassfish) server. Moreover, i'm using jBCrypt to encrypt users' passwords, so i can't use standard jdbcRealm.
How can i secure my resources?
i'm thinking about custom jdbcRealm, it's the right way? How can i create and use it?
Some existing framework can help me?
Thank you in advance.

I would suggest you to use a framework Apache Shiro. The configuration file is below
[main]
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName = SHA-256
sha256Matcher.hashIterations=1
# base64 encoding
sha256Matcher.storedCredentialsHexEncoded = false
#datasource type
ds = org.apache.shiro.jndi.JndiObjectFactory
#datasourcename
ds.resourceName = cfresource
#datasourcetype
ds.requiredType = javax.sql.DataSource
#configuring jdbc realm
jdbcRealm = com.connectifier.authc.realm.CustomJDBCRealm
jdbcRealm.credentialsMatcher = $sha256Matcher
jdbcRealm.dataSource=$ds
jdbcRealm.userRolesQuery=select name from role where email = ? and isactive=1
jdbcRealm.authenticationQuery=select hash, salt from user where email = ?
jdbcRealm.permissionsLookupEnabled=false
securityManager.realms = $jdbcRealm
#login url
authc.loginUrl = /
#page to redirected to after logout
logout.redirectUrl = /
#page to where to land after login
authc.successUrl = /
#username parameter name in the loginform
authc.usernameParam = username
#password parameter name in the loginform
authc.passwordParam = password
#rememberme parameter name in the loginform
authc.rememberMeParam=rememberme
#cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
#securityManager.cacheManager = $cacheManager
#jdbcRealm.authenticationCachingEnabled = true
[urls]
# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
# the 'authc' filter must still be specified for it so it can process that url's
# login submissions. It is 'smart' enough to allow those requests through as specified by the
# shiro.loginUrl above.
/* = anon
The CustomJDBCRealm overriding JDBCRealm is below
package com.connectifier.authc.realm;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;
import org.apache.shiro.util.SimpleByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* #author kiranchowdhary
*
* Application specific JDBC realm. If required override methods of {#link JdbcRealm} to load users, roles and
* permissions from database.
*
* Do not override configuration in code if it can be done via shiro.ini file.
*/
public class CustomJDBCRealm extends JdbcRealm {
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
public CustomJDBCRealm() {
super();
setSaltStyle(SaltStyle.COLUMN);
}
/**
* overriding the method which is in JdbcRealm. If SaltStyle is COLUMN, then gets String salt value from database
* and forms salt byte array of type {#link ByteSource} with decoded string salt value and sets it to salt value of
* AuthenticationInfo.
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// Null username is invalid
if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm.");
}
Connection conn = null;
SimpleAuthenticationInfo info = null;
try {
conn = dataSource.getConnection();
String password = null;
String salt = null;
switch (saltStyle) {
case NO_SALT:
case CRYPT:
case EXTERNAL:
return super.doGetAuthenticationInfo(token);
case COLUMN:
String[] queryResults = getPasswordForUser(conn, username);
password = queryResults[0];
salt = queryResults[1];
break;
}
if (password == null) {
throw new UnknownAccountException("No account found for user [" + username + "]");
}
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
if (salt != null) {
info.setCredentialsSalt(new SimpleByteSource(Base64.decode(salt)));
}
} catch (SQLException e) {
final String message = "There was a SQL error while authenticating user [" + username + "]";
if (log.isErrorEnabled()) {
log.error(message, e);
}
// Rethrow any SQL errors as an authentication exception
throw new AuthenticationException(message, e);
} finally {
JdbcUtils.closeConnection(conn);
}
return info;
}
private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
String[] result;
boolean returningSeparatedSalt = false;
switch (saltStyle) {
case NO_SALT:
case CRYPT:
case EXTERNAL:
result = new String[1];
break;
default:
result = new String[2];
returningSeparatedSalt = true;
}
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(authenticationQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results - although we are only expecting one result,
// since usernames should be unique
boolean foundResult = false;
while (rs.next()) {
// Check to ensure only one row is processed
if (foundResult) {
throw new AuthenticationException("More than one user row found for user [" + username
+ "]. Usernames must be unique.");
}
result[0] = rs.getString(1);
if (returningSeparatedSalt) {
result[1] = rs.getString(2);
}
foundResult = true;
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return result;
}
}

Related

How to get user credentials from request?

I am working on Restlet tutorial example concerning coarse-grained authorization:
public class MyApiWithRoleAuthorization extends Application {
//Define role names
public static final String ROLE_USER = "user";
public static final String ROLE_OWNER = "owner";
#Override
public Restlet createInboundRoot() {
//Create the authenticator, the authorizer and the router that will be protected
ChallengeAuthenticator authenticator = createAuthenticator();
RoleAuthorizer authorizer = createRoleAuthorizer();
Router router = createRouter();
Router baseRouter = new Router(getContext());
//Protect the resource by enforcing authentication then authorization
authorizer.setNext(Resource0.class);
authenticator.setNext(baseRouter);
//Protect only the private resources with authorizer
//You could use several different authorizers to authorize different roles
baseRouter.attach("/resourceTypePrivate", authorizer);
baseRouter.attach("/resourceTypePublic", router);
return authenticator;
}
private ChallengeAuthenticator createAuthenticator() {
ChallengeAuthenticator guard = new ChallengeAuthenticator(
getContext(), ChallengeScheme.HTTP_BASIC, "realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(this, ROLE_USER));
User owner = new User("owner", "owner");
realm.getUsers().add(owner);
realm.map(owner, Role.get(this, ROLE_OWNER));
//Attach verifier to check authentication and enroler to determine roles
guard.setVerifier(realm.getVerifier());
guard.setEnroler(realm.getEnroler());
return guard;
}
private RoleAuthorizer createRoleAuthorizer() {
//Authorize owners and forbid users on roleAuth's children
RoleAuthorizer roleAuth = new RoleAuthorizer();
roleAuth.getAuthorizedRoles().add(Role.get(this, ROLE_OWNER));
roleAuth.getForbiddenRoles().add(Role.get(this, ROLE_USER));
return roleAuth;
}
private Router createRouter() {
//Attach Server Resources to given URL
Router router = new Router(getContext());
router.attach("/resource1/", Resource1.class);
router.attach("/resource2/", Resource2.class);
return router;
}
public static void main(String[] args) throws Exception {
//Attach application to http://localhost:9000/v1
Component c = new Component();
c.getServers().add(Protocol.HTTP, 9000);
c.getDefaultHost().attach("/v1", new MyApiWithRoleAuthorization());
c.start();
}
}
I create a class for checking user credentials:
public class Resource1 extends ServerResource{
#Get
public String represent() throws Exception {
User user = getRequest().getClientInfo().getUser();
String identifier = user.getIdentifier();
char[] pass = user.getSecret();
return this.getClass().getSimpleName() + " found ! User: " + identifier +
"; password = " + charArrayToString(pass) ;
}
private String charArrayToString(char[] chars ) {
String result = "";
for (char c : chars){
result += c;
}
return result;
}
}
When I go to resource http://localhost:9000/v1/resourceTypePublic/resource1/ the application asks for credentials and I input "user", "user" (or "owner", "owner"). But I get internal server error. The reason is that variable pass in return statement
return this.getClass().getSimpleName() + " found ! User: " + identifier +
"; password = " + charArrayToString(pass) ;
has null value. The statement without this variable works ok:
return this.getClass().getSimpleName() + " found ! User: " + identifier;
and returns user login. But what about the secret? Why it returns null value despite the user secret had been inputted?
User object created with statement
User user = getRequest().getClientInfo().getUser();
does not contain information about password despite it has a field secret. There is another way to get user credentials:
char[] pass = getChallengeResponse().getSecret();

how to retrieve the attribute "unicodePwd" in Active Directory through java programming

First of all, I apology for my bad english. I'm brazilian, so if there is any mistakes at the text, please, just disconsidered.
I read a lot of articles here about retrieving the attribute "unicodePwd" in Active Directory, but none of then actually helped me out.
Well, why do I need that information? I'll explain:
I have here some java routines that unify user information from differents systems one to another.
This routines get the information needed in a main Oracle Database and set the information in another Databases (Oracle and MySQL, basically).
For example: We have a private cloud system, that runs in a CentOS Linux OS, that has it own MySQL Database. To unify the users informations, including the users passwords, we get the information from the main Oracle Database and set do this system's MySQL Database, to unify user details and login information.
All the routines that i have here are working and there's no problems, but now we have a new challenge.
We need to do the same unification with ours Active Directory users, getting the information needed in this main Oracle Database and then setting all the information into Active Directory users, including the users passwords.
I already updated the password succesfully in Active Directory users, but I don't want that the password get updated everytime that this java routine runs, but only when the password changes in the main Oracle Database.
Example: When one of the users change the password in the main Oracle Database, the java routine gets this user information to set then in the same user in Active Directory. To do that properly, the routine gets the same information in Active Diretory, then it compares both passwords (Oracle's password and Active Diretory's password) and finally, if the password is different, the routine will update it, but if the password is not different, the routine will do nothing.
That is why i need to retrieve the attribute "unicodePwd" in Active Directory.
Here is some of my code:
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import org.apache.commons.mail.EmailException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
public class ldapQuery {
String distinguishedName = "";
String department = "";
String physicalDeliveryOfficeName = "";
String telephoneNumber = "";
String mobile = "";
String title = "";
String sAMAccountName = "";
String unicodePwd = "";
public ldapQuery(String mail) {
try {
final Hashtable<String, String> env = new Hashtable<String, String>();
final String adminName = "CN=MY DOMAIN ADMIN,CN=MY DOMAIN ADMIN FOLDER LOCALIZATION,DC=MY DOMAIN,DC=MY DOMAIN,DC=MY DOMAIN";
final String adminPasswd = "MY DOMAIN ADMIN PASSWORD";
final String ldapUrl = "ldaps://MY ACTIVE DIRECTORY SERVER:636";
final String factory = "com.sun.jndi.ldap.LdapCtxFactory";
final String authType = "simple";
final String protocol = "ssl";
env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
env.put(Context.SECURITY_AUTHENTICATION, authType);
env.put(Context.SECURITY_PRINCIPAL, adminName);
env.put(Context.SECURITY_CREDENTIALS, adminPasswd);
env.put(Context.SECURITY_PROTOCOL, protocol);
env.put(Context.PROVIDER_URL, ldapUrl);
DirContext ctx = new InitialLdapContext (env,null);
SearchControls searchCtls = new SearchControls();
String returnedAtts[] = {"sAMAccountName", "distinguishedName","department", "physicalDeliveryOfficeName", "telephoneNumber", "mobile", "title", "unicodePwd"};
searchCtls.setReturningAttributes(returnedAtts);
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String searchFilter = "(&(objectClass=user)(mail=" + mail +"))";
String searchBase = "DC=MY DOMAIN,DC=MY DOMAIN,DC=MY DOMAIN";
int totalResults = 0;
NamingEnumeration<SearchResult> answer =ctx.search(searchBase, searchFilter, searchCtls);
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult)answer.next();
totalResults++;
Attributes attrs = sr.getAttributes();
if (attrs != null) {
distinguishedName = (String) attrs.get("distinguishedName").get();
department = (String) attrs.get("department").get();
physicalDeliveryOfficeName = (String) attrs.get("physicalDeliveryOfficeName").get();
telephoneNumber = (String) attrs.get("telephoneNumber").get();
mobile = (String) attrs.get("mobile").get();
title = (String) attrs.get("title").get();
sAMAccountName = (String) attrs.get("sAMAccountName").get();
Attribute passwd = attrs.get("unicodePwd");
unicodePwd = unicodePwd + passwd;
if (department == null) {
department = "";
}
if (physicalDeliveryOfficeName == null) {
physicalDeliveryOfficeName = "";
}
if (telephoneNumber == null) {
telephoneNumber = "";
}
if (mobile == null) {
mobile = "";
}
if (title == null) {
title = "";
}
}
}
}
catch (NamingException e){
System.err.println("FAIL MESSAGE: " + e);
}
}
public String ldapSearchResultDistinguishedName() {
return distinguishedName;
}
public String ldapSearchResultDepartment() {
return department;
}
public String ldapSearchResultPhysicalDeliveryOfficeName() {
return physicalDeliveryOfficeName;
}
public String ldapSearchResultTelephoneNumber() {
return telephoneNumber;
}
public String ldapSearchResultMobile() {
return mobile;
}
public String ldapSearchResultTitle() {
return title;
}
public String ldapSearchResultUnicodePwd() {
return unicodePwd;
}
public String ldapSearchResultSAMAccountName() {
return sAMAccountName;
}
}
After running the code, all the variables return the correct information but the variable "unicodePwd", that returns "null", even though the user has a password.
I know about the byte UTF-16LE thing and that the "unicodePwd" field in Active Directory is encrypted, but, as I explained earlier, i need that information decrypted in a String variable.
Any ideias?
Thank you!
I know this is an old question but I stumbled across it as I was also looking for an answer to the same question. I found the answer and thought it might help anybody else who lands here.
According to Microsoft Documentation it would appear that the unicodePwd attribute is NEVER returned by an LDAP search.
In my case, I need to validate that the credentials received are correct. So my plan is to use the username/password received and create a custom LdapContextFactory on the fly with those credentials. If I can contact the server successfully by doing an LdapContextFactory.get and get back an LdapContext then I can be certain that the password supplied was correct. If you don't get it back then you know it's wrong and can take it from there.

Active Directory LDAP Authentication with Tomcat

Its been a day since I started working with Active Directory LDAP with Tomcat server.
I have not seen a clear and simple example (like a login module) of using Active Directory LDAP with Tomcat and moreover I just got the below details from the Administrator for the LDAP server that I access.
The below code looks simple, but I am stuck with the below exception.
String server = "192.168.71.116"; // Server hostname
int port = 50001;
String basedn = "DC=cblan-test,DC=mblox,DC=com";
I pass in the username and password which are picked from the request object.
This is the main piece of code that I use, I got this example from here
<%
String user = request.getParameter("user");
String password = request.getParameter("password");
String filter = "(|(uid=" + user + ")" + "(mail=" + user + "#*))";
String cliEquiv = "<tt>ldapsearch -h " + server + " -p " +
port + " -b " + basedn + " \"" + filter + "\"</tt></p>";
%>
<p>Equivalent command line:<br /><%= cliEquiv%><hr />
<%
// Connect to the LDAP server.
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://" + server + ":" + port + "/");
// Search and retrieve DN.
try {
LdapContext ldap = new InitialLdapContext(env, null);
NamingEnumeration results = ldap.search(basedn, filter, null);
String binddn = "None";
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
binddn = sr.getName() + "," + basedn;
}
%>
<p>Bind DN found: <%= binddn%><hr /></p>
<%
ldap.close();
// Authenticate
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, binddn);
env.put(Context.SECURITY_CREDENTIALS, password);
ldap = new InitialLdapContext(env, null);
%>
<p>Successful authentication for <%= user%>.</p>
This is my LDAP server details
I get the below exception which I dont really understand and I have tried many suggestions but nothing fruitful. Could anyone please help me fix this, it would help me proceed with building up my app based on this. Please also give your suggestions on authentication with Active Directory LDAP in Tomcat.
Sep 17, 2013 1:40:32 PM org.apache.catalina.realm.JNDIRealm authenticate
SEVERE: Exception performing authentication
javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C09062B, comment: In order to perform this operation a successful bind must be completed on the connection.,
data 0, va28
Note: the filter you used UID while this attribute is not supported nativly in AD
second check below code to be able to connect the right way
package lib;
/**
* #author sghaida
*
*/
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.security.cert.CertificateException;
import ccc.gr.moa.server.FTPMIServiceImpl;
import com.extjs.gxt.ui.client.data.BaseModel;
public class ADConnector {
/**
* #param args
*/
#SuppressWarnings("unchecked")
static Hashtable<String, String> envGC = new Hashtable();
static String adminName;
static String adminPassword;
static String urlGC;
static String searchBase;
static LdapContext ctxGC;
public ADConnector() throws NamingException
{
//get AD properties
urlGC = "ldap://" + FTPMIServiceImpl.ADProperties.get("ADHostname")+ ":3268";
adminName = FTPMIServiceImpl.ADProperties.get("bindDN");
adminPassword = FTPMIServiceImpl.ADProperties.get("bindPassword");
searchBase = FTPMIServiceImpl.ADProperties.get("searchBase");
envGC.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//envDC.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//set security credentials, note using simple cleartext authentication
envGC.put(Context.SECURITY_AUTHENTICATION,"simple");
envGC.put("java.naming.ldap.attributes.binary","userCertificate");
envGC.put(Context.SECURITY_PRINCIPAL,adminName);
envGC.put(Context.SECURITY_CREDENTIALS,adminPassword);
//envDC.put(Context.SECURITY_AUTHENTICATION,"simple");
//envDC.put(Context.SECURITY_PRINCIPAL,adminName);
//envDC.put(Context.SECURITY_CREDENTIALS,adminPassword);
//connect to both a GC and DC
envGC.put(Context.PROVIDER_URL,urlGC);
//envDC.put(Context.PROVIDER_URL,urlDC);
//Create the initial directory context for both DC and GC
ctxGC = new InitialLdapContext(envGC,null);
//ctxDC = new InitialLdapContext(envDC,null);
}
/**
* #param name
* #return
* #throws NamingException
*/
/**
* #param name
* #return
* #throws NamingException
*/
public List<BaseModel> searchResults(String searchFilter ) throws NamingException
{
//Create the search controls
SearchControls searchCtls = new SearchControls();
//Specify the attributes to return
//String returnedAtts[]={"sn","givenName","mail","userCertificate"};
String returnedAtts[]={"cn","sn","givenName","sAMAccountName","mail","distinguishedName"};
searchCtls.setReturningAttributes(returnedAtts);
//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//Specify the Base for the search
//String searchBase = "dc=ccg,dc=local";
//initialize counter to total the results
int totalResults = 0;
//Search for objects in the GC using the filter
NamingEnumeration answer = ctxGC.search(searchBase, searchFilter, searchCtls);
List<BaseModel> results = new ArrayList<BaseModel>();
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult)answer.next();
totalResults++;
// Print out some of the attributes, catch the exception if the attributes have no values
Attributes attrs = sr.getAttributes();
if (attrs != null) {
try {
System.out.println(" cn(GC): " + attrs.get("cn").get());
System.out.println(" sn(GC): " + attrs.get("sn").get());
System.out.println(" givenName(GC): " + attrs.get("givenName").get());
System.out.println(" mail(GC): " + attrs.get("mail").get());
System.out.println(" sAMAccountName(GC): " + attrs.get("sAMAccountName").get());
System.out.println(" distinguishedName(GC): " + attrs.get("distinguishedName").get());
BaseModel bm = new BaseModel();
bm.set("full_name", attrs.get("cn").get());
bm.set("last_name", attrs.get("sn").get());
bm.set("first_name", attrs.get("givenName").get());
bm.set("email",attrs.get("mail").get());
bm.set("account_name", attrs.get("sAMAccountName").get());
results.add(bm);
}
catch (NullPointerException e) {
System.err.println("Problem listing attributes from Global Catalog: " + e);
e.printStackTrace();
}
}
}
ctxGC.close();
return results;
}
public static void main(String[] args) throws CertificateException, NamingException {
ADConnector connector = new ADConnector();
//specify the LDAP search filter
String searchFilter = "(sAMAccountName=sghaida)";
List<BaseModel> results = connector.searchResults(searchFilter);
}
}

Can I use MyBatis to generate Dynamic SQL without executing it?

I have some complex queries to build with a number of optional filters, for which MyBatis seems like an ideal candidate for generating dynamic SQL.
However, I still want my query to execute in the same framework as the rest of the application (which is not using MyBatis).
So what I was hoping to do was use MyBatis strictly for generating the SQL, but from there using the rest of my app to actually execute it. Is this possible? If so, how?
Although MyBatis was designed to execute the query after it builds it, you can make use of it's configuration and a little bit of "inside knowledge" to get to what you need.
MyBatis is a very nice framework, unfortunately it lacks on the documentations side so the source code is you friend. If you dig around you should bump into these classes: org.apache.ibatis.mapping.MappedStatement and org.apache.ibatis.mapping.BoundSql which are key players into building the dynamic SQL. Here is a basic usage example:
MySQL table user with this data in it:
name login
----- -----
Andy a
Barry b
Cris c
User class:
package pack.test;
public class User {
private String name;
private String login;
// getters and setters ommited
}
UserService interface:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(int loginNumber);
}
UserService.xml mapper file:
<mapper namespace="pack.test.UserService">
<select id="getUser" resultType="pack.test.User" parameterType="int">
<!-- dynamic change of parameter from int index to login string -->
select * from user where login = <choose>
<when test="_parameter == 1">'a'</when>
<when test="_parameter == 2">'b'</when>
<otherwise>'c'</otherwise>
</choose>
</select>
</mapper>
sqlmap-config.file:
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/test"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="pack/test/UserService.xml"/>
</mappers>
</configuration>
AppTester to show the result:
package pack.test;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class AppTester {
private static String CONFIGURATION_FILE = "sqlmap-config.xml";
public static void main(String[] args) throws Exception {
Reader reader = null;
SqlSession session = null;
try {
reader = Resources.getResourceAsReader(CONFIGURATION_FILE);
session = new SqlSessionFactoryBuilder().build(reader).openSession();
UserService userService = session.getMapper(UserService.class);
// three users retreived from index
for (int i = 1; i <= 3; i++) {
User user = userService.getUser(i);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(i); // parameter for the SQL statement
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
}
} finally {
if (reader != null) {
reader.close();
}
if (session != null) {
session.close();
}
}
}
}
And the result:
Retreived user: Andy a
SQL used: select * from user where login = 'a'
Retreived user: Barry b
SQL used: select * from user where login = 'b'
Retreived user: Cris c
SQL used: select * from user where login = 'c'
Everyone knows how to use BoundSql.getSql() to get a paramaterized query string from MyBatis, like this:
// get parameterized query
MappedStatement ms = configuration.getMappedStatement("MyMappedStatementId");
BoundSql boundSql = ms.getBoundSql(parameters);
System.out.println("SQL" + boundSql.getSql());
// SELECT species FROM animal WHERE name IN (?, ?) or id = ?
But now you need the other half of the equation, the list of values that correspond to the question marks:
// get parameters
List<ParameterMapping> boundParams = boundSql.getParameterMappings();
String paramString = "";
for(ParameterMapping param : boundParams) {
paramString += boundSql.getAdditionalParameter(param.getProperty()) + ";";
}
System.out.println("params:" + paramString);
// "Spot;Fluffy;42;"
Now you can serialize it to send elsewhere to be run, or you can print it to a log so you can stitch them together and run the query manually.
*code not tested, might be minor type issues or the like
mybatis version is 3.4.5
Util Class
To convert mapper to sql, need mapper interface class,method name,paramters,and sqlSession.
package util;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import org.apache.ibatis.binding.MapperMethod.MethodSignature;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.util.CollectionUtils;
/**
* #author zwxbest - 19-4-25
*/
public class SqlUtil {
public static String showSql(SqlSession sqlSession, Class mapperInterface, String methodName,
Object[] params) {
Configuration configuration = sqlSession.getConfiguration();
MappedStatement ms = configuration.getMappedStatement(
mapperInterface.getName() + "." + methodName);
Method sqlMethod = null;
//find method equals methodName
for (Method method : mapperInterface.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
sqlMethod = method;
break;
}
}
if (sqlMethod == null) {
throw new RuntimeException("mapper method is not found");
}
MethodSignature method = new MethodSignature(configuration, mapperInterface, sqlMethod);
Object paramObject = method.convertArgsToSqlCommandParam(params);
BoundSql boundSql = ms.getBoundSql(paramObject);
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(
parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
sql = sql.replaceFirst("\\?", "missing");
}
}
}
}
return sql;
}
/**
* if param's type is `String`,add single quotation<br>
*
* if param's type is `datetime`,convert to string and quote <br>
*/
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat
.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else if (obj instanceof LocalDateTime) {
value = "\'" + ((LocalDateTime) obj)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
}
call example
sqlSession is injected by Spring .
#Autowired
private SqlSession sqlSession;
String sql = SqlUtil
.showSql(sqlSession, PromotionCodeMapper.class, "selectByPromotionCodeForUpdate",
new Object[]{"111"});
log.warn(sql);
public static void main(String[] args) throws Exception {
String script = "<script>select * from table where 1 = 1<if test='id != null'>and id = ${id} </if></script>";
System.out.println(buildSql(script));
}
private static String buildSql(String script) {
LanguageDriver languageDriver = new XMLLanguageDriver();
Configuration configuration = new Configuration();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);
Map<String, String> parameters = new HashMap<>();
parameters.put("id", "1");
BoundSql boundSql = sqlSource.getBoundSql(parameters);
return boundSql.getSql();
}
use ${id} instead of #{id}
result is: select * from table where 1 = 1 and id = 1
Just to add to Bogdan's correct answer: You need to pass a JavaBean to getBoundSql() with getter's for your interface parameters, if you're interface has a more complex signature.
Let's assume you want to query the user based on the login number and/or the user name. Your interface might look like this:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(#Param("number") int loginNumber, #Param("name") String name);
}
I'm leaving out the Mapper code since it's irrelevant for this discussion, but your code in AppTester should become:
[...]
final String name = "Andy";
User user = userService.getUser(i, name);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(new Object() {
// provide getters matching the #Param's in the interface declaration
public Object getNumber() {
return i;
}
public Object getName() {
return name;
}
});
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
[...]

Sending a response from PHP to an Android/Java mobile app?

I currently have a piece of code in my Android application that picks up the devices IMEI and sends that IMEI as a parameter to a PHP script that is hosted on the Internet.
The PHP script then takes the IMEI parameter and checks a file to see if the IMEI exists in the file, if it does I want to be able to let my Android application know that the IMEI exists. So essentially I just want to be able to return True to my application.
Is this possible using PHP?
Here is my code so far:
Android/Java
//Test HTTP Get for PHP
public void executeHttpGet() throws Exception {
BufferedReader in = null;
try {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.setURI(new URI("http://testsite.com/" +
"imei_script.php?imei=" + telManager.getDeviceId()
));
HttpResponse response = client.execute(request);
in = new BufferedReader
(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
String page = sb.toString();
System.out.println(page);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
The above sends the IMEI as a parameter to the PHP script which picks it up successfully and runs a check against the file successfully, however I neeed to then be able to send a positive response back from the PHP script if the IMEI matches one in the file.
Here is the PHP:
<?php
// to return plain text
header("Content-Type: plain/text");
$imei = $_GET["imei"];
$file=fopen("imei.txt","r") or exit("Unable to open file!");
while(!feof($file))
{
if ($imei==chop(fgets($file)))
echo "True";
}
fclose($file);
?>
So instead of echo True I want to be able to let my application know that the IMEI was found, is this possible and if so what should I be using to achieve it?
this is good stuff! actually, you're nearly there. your php shouldn't change, your java should! you just need to check the result of the response inside your java code. redeclare your java method as
public String executeHttpGet() {
then, let this method return the variable page.
now you can create a helper method somewhere. if you put it in the same class as executeHttpGet, it will look like this:
public boolean imeiIsKnown(){
return executeHttpGet().equals("True");
}
now you can call this method to find out if your imei is known in your php backend.
I'm not sure is it good for you or not - but you can use headers. If the IMEI was found you can send header("Status: HTTP/1.1 200 OK") otherwise send header("Status: 404 Not Found").
And then you should check response status in your application.
your code is basically sound, all you need to do is tweak it up a bit. i mixed and matched the answers above, because i needed to accomplish exactly what you were trying to. i created a database, instead of checking txt files.
CREATE TABLE IF NOT EXISTS `user_device` (
`Id_User_Device` int(11) NOT NULL auto_increment,
`Nr_User_Device` varchar(60) collate utf8_bin NOT NULL,
`Ic_User_Device_Satus` int(11) NOT NULL default '1',
PRIMARY KEY (`Id_User_Device`),
KEY `Nr_User_Device` (`Nr_User_Device`,`Ic_User_Device_Satus`)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=20 ;
the java android code would be (dont forget to create the proper adjustements in the main.xml layout file, inserting 2 elements to a classical helloworld screen:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class ZdeltestEMEIActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
DeviceUuidFactory deviceUuidFactory = new DeviceUuidFactory(this);
String deviceUuid = deviceUuidFactory.getDeviceUuid().toString();
Log.d("tgpost",deviceUuid);
try {
String webPostAnswer = deviceIdCheck(deviceUuid);
if (webPostAnswer != null) {
TextView tv1 = (TextView) findViewById(R.id.textdisplay01);
TextView tv2 = (TextView) findViewById(R.id.textdisplay02);
tv1.setText(webPostAnswer);
tv2.setText(deviceUuid);
Log.d("tgpost", "okok "+webPostAnswer);
} else {
Log.d("tgpost", "nono empty");
}
} catch (Exception e) {
// TODO Auto-generated catch block
Log.i("tgpost", "exc " + e.getMessage());
Log.i("tgpost", e.toString());
Log.e("tgpost", e.getStackTrace().toString());
e.printStackTrace();
}
}
public String deviceIdCheck(String deviceUuidIn) throws Exception {
boolean flagOK = false;
BufferedReader in = null;
try {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
Log.v("tgpost", "okok");
//"imei_script.php?deviceId="; + telManager.getDeviceId()
request.setURI(new URI("http://www.you.net/" +
"deviceIdCheck.php?deviceId=" + deviceUuidIn
));
HttpResponse response = client.execute(request);
Log.d("tgpost", "php answered> "+response);
in = new BufferedReader
(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
String page = sb.toString();
Log.d("tgpost", "php answered HUMAN> "+page);
return page;
} catch (Exception e) {
return "problems with connection "+e.getMessage();
}
}
}
with an addtional class
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected static UUID uuid;
public DeviceUuidFactory(Context context) {
if( uuid ==null ) {
synchronized (DeviceUuidFactory.class) {
if( uuid == null) {
final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null );
if (id != null) {
// Use the ids previously computed and stored in the prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case fallback on deviceId,
// unless it's not available, then fallback on a random number which we store
// to a prefs file
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
} else {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely"
* to be unique across all Android devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
* on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a
* usable value.
*
* In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID
* may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
* to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
* change after a factory reset. Something to be aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
*
* #see http://code.google.com/p/android/issues/detail?id=10603
*
* #return a UUID that may be used to uniquely identify your device for most purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}
on the php side:
<?php
// to return plain text
// header("Content-Type: plain/text");
include('/home/public_html/ConnStrDB.php');
$deviceId = $_GET["deviceId"];
$sql = "SELECT Nr_User_Device FROM user_device WHERE Nr_User_Device = '".$deviceId."'";
$result = mysql_query($sql);
if ($result) {
$row = mysql_fetch_array($result);
if ($row[0]) {$deviceIdFile = $row[0];} else {$deviceIdFile = "device not found";}
} else {
$deviceIdFile = "no check was made, empty set";
}
echo $_GET["deviceId"]." ".$deviceIdFile;
?>
and (so that you dont have to insert the numbers manually (just change the php fileName in the submit):
<?php
// to return plain text
// header("Content-Type: plain/text");
include('/home/public_html/ConnStrDB.php');
$deviceId = $_GET["deviceId"];
$sql = "SELECT Nr_User_Device, Ic_User_Device_Status FROM user_device WHERE Nr_User_Device = ".$deviceId;
$sql = "INSERT INTO user_device (Nr_User_Device) VALUES ('".$deviceId."')";
$result = mysql_query($sql);
if ($result) {
$deviceIdFile = "device inserted";
} else {
$deviceIdFile = "not inserted";
}
echo $_GET["deviceId"]." ".$deviceIdFile;
?>
if succesful, your mobile screen will display the imei 3 times (the one on the device, the one received in php and the one retrieved on the database).
ConnStrDB.php is a file that contains your complete connection to MySQL database.
if you reply with long text, the android application will receive it, as well as the verbose version of any php warning. if you dont need json, you can answer any xml thru a php echo. thanx for your question, very useful! and thanx for the EXCELLENT answers!

Categories