Writing to Different Log Files Example using Log4j

One of the common requirement in Java projects, that are using Log4j, and want to write logs into different files for each module (or layer) in the project. For example, if you have a an application and you may want to log the messages from the service layer to a service.log file and the log messages from the DAO layer to the DAO.log file and so on. This is very simple to achieve using Log4j. EVEN you can differentiate logs for EACH Java class with the project.



log.properties

# Root logger option
log4j.rootLogger=INFO, stdout, file
# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %-5p %m%n
# Redirect log messages to a log file, support file rolling.
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=logs/default_Logs.log
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=5
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%d{yyyy-MM-dd HH\:mm\:ss}] %-5p %m%n

Now call the below method loadLog4jConfig(this) from the class for which you want to differentiate log file. A common practice is to call the below method from the constructor of the class. It will runtime generate a log file named as that of class name and will write all corresponding logs into this particular log file. 

public static void loadLog4jConfig(Object obj)

 {

 try

 {

     Class<?> thisClass = (obj.getClass());

     String CLASS_NAME = thisClass.getSimpleName();

   

     String logFileName = "logs/" + CLASS_NAME + ".log";

   

     Properties props = new Properties();

     InputStream configStream = thisClass.getResourceAsStream("/log4j.properties");

     props.load(configStream);

     configStream.close();

   

     props.setProperty("log4j.appender.file.File", logFileName);

     LogManager.resetConfiguration();

    PropertyConfigurator.configure(props);

 }

 catch (Exception e)

 {

    e.printstacktrac();

 }

 }

Note: There are so many others ways also, however the above method is the simplest way to write logs into different files.


Passing Extra Login Fields with Spring Security

In Spring Security, it just support to receive only 2 parameters i.e. user_name and password by default. Now, we have a scenario where login form includes login_type as well as a third field. We just want to pass more one parameter along with this login form.


There's a number of ways to achieve this. Most used method is by extending UsernamePasswordAuthenticationFilter class and creating your custom filters. However this is complex and long 
procedure, lot of code required.

In this article I am going to explore two easiest ways:

Method 1:


Add the following listener in web.xml.

Note: The order of listener should be first one, if are there many listener.

Step (i)
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>
<context-param>
    <param-name>loginType</param-name>
    <param-value></param-value>
</context-param>

Step(ii)


Now add below code snippet in your java code where you want to get this additional parameter. In my case I am getting it in AuthenticationProvider's Authentication authenticate() method as below:

RequestAttributes attribs = RequestContextHolder.getRequestAttributes();

HttpServletRequest request = null;
  
if (RequestContextHolder.getRequestAttributes() != null) {
    request = ((ServletRequestAttributes) attribs).getRequest();
}
  
System.out.println("extra param : "+ request.getParameter("loginType"));

Method 2:


Another easiest way if you are using Custom AuthenticationProvider. You can just inject HttpServletRequest and retrieve your extra parameter:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

@Autowired(required = false)
private HttpServletRequest request;

@Autowired
UserService userService;

@Override
public Authentication authenticate(Authentication authentication) 
{
   System.out.println("request testing= " + request.getParameter("loginType"));
}

@Override
public boolean supports(Class<?> authentication) {
 return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}


Hope You enjoy both above methods. :)

AES-128 Example in JAVA

The example given will accomplish below Tasks.

  • Generate symmetric key using AES-128.
  • Generate initialization vector used for CBC (Cipher Block Chaining).
  • Encrypt message using symmetric key and initialization vector.
  • Decrypt the encrypted message using symmetric key and initialization vector.

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESManager {

 public static String ALGORITHM = "AES";
 // public static String AES_CBC_NoPADDING = "AES/CBC/NoPadding";
 public static String AES_CBC_PADDING = "AES/CBC/PKCS5Padding";

 public static byte[] encrypt(final byte[] key, final byte[] IV, final byte[] message) throws Exception {
  return AESManager.encryptDecrypt(Cipher.ENCRYPT_MODE, key, IV, message);
 }

 public static byte[] decrypt(final byte[] key, final byte[] IV, final byte[] message) throws Exception {
  return AESManager.encryptDecrypt(Cipher.DECRYPT_MODE, key, IV, message);
 }

 private static byte[] encryptDecrypt(final int mode, final byte[] key, final byte[] IV, final byte[] message) throws Exception {
  final Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
  final SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
  final IvParameterSpec ivSpec = new IvParameterSpec(IV);
  cipher.init(mode, keySpec, ivSpec);
  return cipher.doFinal(message);
 }

 public static String getHex(byte[] data, int length) {
  StringBuffer sb = new StringBuffer();
  for (int i = 0; i < length; i++) {
   String hexStr = Integer.toHexString(((int) data[i]) & 0xFF);
   if (hexStr.length() < 2) {
    sb.append("0").append(hexStr.toUpperCase());
   } else {
    sb.append(hexStr.toUpperCase());
   }
  }
  return sb.toString();
 }

}
AESClient.java: AESClient class will generate random input message and will invoke AESManager.java to encrypt & decrypt input message.
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.UUID;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class AESClient {

 private static int AES_128 = 128;
 private static int AES_192 = 192;
 private static int AES_256 = 256;

 public static void main(String[] args) throws Exception {

  byte keyBytes[] = generateKey();

  String AES_KEY_HEX = AESManager.getHex(keyBytes, keyBytes.length);
  System.out.println("AES-KEY : " +AES_KEY_HEX);

  // Initialization vector
  byte IVBytes[] = generateKey();

  String randomString = UUID.randomUUID().toString().substring(0, 16);
  System.out.println("1. Original Message: " + randomString);

  byte[] cipherText = AESManager.encrypt(keyBytes, IVBytes, randomString.getBytes());
  System.out.println("2. Encrypted Text: " + Base64.getEncoder().encodeToString(cipherText));

  byte[] decryptedString = AESManager.decrypt(keyBytes, IVBytes, cipherText);
  System.out.println("3. Decrypted Message : " + new String(decryptedString));
 }

 public static byte[] generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
  KeyGenerator keyGenerator = KeyGenerator.getInstance(AESManager.ALGORITHM);
  keyGenerator.init(AES_128);
  SecretKey key = keyGenerator.generateKey();
  return key.getEncoded();
 }
}
Sample outPut:
AES-KEY : 6239A5DBBA65D0D42E6520922621A8B8
1. Original Message: 5dbf850f-3938-48
2. Encrypted Text: smV12iTwLIHNTgFRSDzG2xmzYl5yRJQ6Jo2qnqK0iqc=
3. Decrypted Message : 5dbf850f-3938-48Generate symmetric key using AES-128.

Important Note:

AES uses block size of 16 bytes (128 bits), so if you are using "AES/CBC/NoPadding", then Smaller input must be padded with zeros to 16 bytes otherwise you will get below exception:

javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes


In order to avoid padding at your end, then you have to use "AES/CBC/PKCS5Padding" as shown in above example.