Sunday, March 6, 2011

Tự xây dựng 1 security framework

Nguyên lý Security
Xây dựng security cho một ứng dụng phần mềm gần như là một yêu cầu bắt buộc đối với tất cả các hệ thống phần mềm. Phân hệ security này có hai nhiệm vụ chính: a) kiểm tra xem người dùng đăng nhập có cung cấp đúng username và password không b) nếu đúng thì kiểm tra người dùng đã đăng nhập có quyền cập nhật, xóa hoặc xem một tài nguyên (địa chỉ url, file, phương thức) nào đó hay không. Hai nhiệm vụ này có khi đi kèm với nhau hoặc có khi tách rời nhau. Chúng thường được gọi bằng cặp tên: xác thực người dùng (authentication) và kiểm quyền (authorization). Đối với một ứng dụng web, do được cài đặt trên Internet và có khả năng bị truy cập rộng rãi bởi tất cả mọi người nên yêu cầu Secuirty cho nó càng trở nên bức thiết hơn. 

Thông thường, việc xác thực một người dùng sẽ được thực hiện thông qua màn hình Đăng nhập. Khi đó người dùng sẽ cung cấp một cặp tên đăng nhập và mật khẩu. Hệ thống sẽ kiểm tra xem cặp tên đăng nhập/mật khẩu này có tồn tại trong cơ sở dữ liệu (hoặc là quan hệ hoặc là LDAP) để tra về kết quả là đăng nhập thành công hay không. Do mỗi người dùng khác nhau sẽ có các vai trò (roles) và quyền khác nhau (privileges) nên phân hệ security phải có tính năng kiểm quyền xem người dùng có được phép truy cập một tài nguyên nào đó hay không. 

Cách dễ nhất để hiện thực việc kiểm quyền là dùng một đoạn code if ... else. Trước khi truy xuất vào tài nguyên, đoạn code kiểm quyền sẽ được thực thi xem người dùng đang đăng nhập có quyền trên tài nguyên đó không, nếu có thì cho phép tiếp tục làm, nếu không sẽ báo lỗi hoặc chuyển sang một trang thông báo Truy cập không hợp lệ.

Tự xây dựng một security framework
Servlet mặc dù không còn được ưa chuộng để xây dựng một ứng dụng web nhưng lại chính là nền tảng bên dưới của tất cả các MVC web framework như Struts, JSF, SpringMVC. Ngoài ra, servlet còn cung cấp một tiện ích mạnh mẽ cho việc xây dựng ứng dụng web đó chính là lớp Filter. Hiểu một cách nôm na, filter đóng vai trò như một bộ lọc. Filter cho phép lọc các yêu cầu truy cập web theo một mẫu nào đó, ví dụ như *.jsp, *.action, restrictedarea/*. Một đoạn mã trong filter sẽ được thực thi trước khi yêu cầu truy cập web được thực hiện. Theo thiết kế này, filter có thể giúp hiện thực các tính năng của một security framework như đã mô tả ở trên. Ví dụ như ở đoạn mã trong filter ta có thể làm phối hợp cả hai việc xác thực người dùng và kiểm tra quyền.

Để xây dựng framework này ta cần một AuthorizationFilter. Lớp này hiện thực lớp Filter của Java bằng cách nạp chồng phương thức doFilter như sau:

public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws ServletException, IOException {
           
            HttpSession session = ((HttpServletRequest) request).getSession(true);
            User currentUser = (User) session.getAttribute("user");

            if (currentUser == null) {
                  request.getRequestDispatcher("../error.jsp").forward(request, response);
                 
            } else {
                  // Get relevant URI.
                  String URI = ((HttpServletRequest) request).getRequestURI();

                  boolean authorized = AuthorizationManager.isUserAuthorized(currentUser, URI);
                  if (authorized) {
                        chain.doFilter(request, response);
                  } else {
                        request.getRequestDispatcher("../error.jsp").forward(request, response);
                  }
            }
      }
     


Một lớp AuthorizationManager dùng để kiểm tra quyền của user hiện hành.

public  class AuthorizationManager {

      public static  boolean   isUserAuthorized(User user, String url)
      {    
            if (user.getName().equals("admin"))
                  return true;
            else
                  return false;
      }
}

Lớp User chỉ đơn giản bao gồm 2 thuộc tính id và name như sau:

public class User {

      private int id;
     
      private String name;
}


Quan trọng nhất ta cần phải kích hoạt filter cho ứng dụng bằng cách cho thêm đoạn code sau vào trong file web.xml:

<filter>
     <filter-name>AuthorizationFilterfilter-name>
     <filter-class>examples.AuthorizationFilterfilter-class>

filter>

<filter-mapping>
     <filter-name>AuthorizationFilterfilter-name>
     <url-pattern>/restricted/*url-pattern>
filter-mapping>

Đoạn code này báo cho servlet container biết phải lọc tất cả những request tới thư mục /restricted.

Như vậy, việc xây dựng 1 security framework như Spring Security hoàn toàn khả thi và không có gì quá bí hiểm.

Các framework security chính thống:
Acegi (sau này thành Spring Security) là một framework security cho các ứng dụng web rất nổi tiếng và được ưa chuộng trong cộng đồng lập trình Java. Có nhiều người xem đây như một de facto standard. Acegi hay Spring Security trên thực tế cũng sử dụng Servlet Filter để hiện thực cả  hai việc xác thực quyền và kiểm tra quyền. Các web server hoặc application server có cơ chế security riêng thông qua việc sử dụng các realms. Tuy vậy, cách sử dụng security từ các web server trên thực tế không được ưa chuộng vì nó gắn quá chặt với một web server nào đó. Mà điều này thì không có công ty nào muốn. 

Download ví dụ này ở đây: MyWeb.zip - 10.1 KB

Tự xây dựng một framework lập trình hướng khía cạnh (AOP)

Lập trình khía cạnh là gì?
Sự xuất hiện của ngôn ngữ hướng đối tượng là một cuộc cách mạng trong lĩnh vực phần mềm. Nhiều người đánh giá nếu không có phương pháp phân tích và thiết kế hướng đối tượng, ngành công nghiệp phần mềm sẽ rơi vào khủng hoảng bởi sự phức tạp ngày càng gia tăng trong các hệ thống phần mềm. May mắn thay điều đó đã không xảy ra. Sau hơn 30 năm thống trị kĩ nghệ phần mềm, hướng đối tượng có vẻ đã hụt hơi với những yêu cầu cao hơn của các ứng dụng thực tế. Trong bối cảnh đó, lập trình hướng khía cạnh (Aspect Oriented Programming gọi tắt là AOP) dường như là một cứu cánh, và là một sự bổ sung để duy trì sức mạnh của hướng đối tượng.

Vậy hướng khía cạnh là gì? Một ví dụ rất đơn giản sau đây sẽ giúp độc giả hình dung sơ nét về lập trình hướng khía cạnh. Giả sử ta có lớp Dog với phương thức bark() – sủa – cho in ra dòng chữ gâu gâu gâu hoặc phức tạp hơn vừa in ra  vừa trả về câu này.

public class Dog implements Animal {

      public String bark(){
            System.out.println("gau gau gau");
            return "gau gau gau";
      }
}

Giả sử nếu ta muốn kiểm tra (authorize) xem người dùng hiện hành có được phép thực thi phương thức này không ta cần phải:

public String bark(){

            if(currentUser.isAuthorized())
{
            System.out.println("gau gau gau");
            return "gau gau gau";
}
else
      throws “Invalid Access Exception”  
      }

Rõ ràng, việc kiểm tra quyền không nằm chung logic với bark(). Việc nhúng đoạn code này vào trong bark khiến cho chương trình rối rắm và khó bảo trì sau này. Cách tốt nhất để làm chuyện này là cho phép một cơ chế Đánh chặn (Interception): trước khi phương thức này được gọi việc kiểm quyền của người dùng sẽ được thực thi . Đoạn mã kiểm sẽ được đặt ở đâu đó trong chương trình, tách riêng (decouple) ra khỏi bark().

Java 1.3 cho phép làm được điều này thông qua DynamicProxy. Trên thực tế Spring AOP sử dụng phương pháp này cònAspectJ và JBOSS AOP sử dụng việc pha trộn lớp (class-weaving).

Tự hiện thực 1 AOP framework
Sau đây ta sẽ tự viết code để làm AOP mà không nhờ vào bất cứ một thư viện thứ ba nào (third-party libraries).

Trước tiên ta có 1 lớp tên là MyProxyFactory, một nhà máy chuyên sản xuất các proxy. Lớp này có 1 phương thức duy nhất là createProxy(). Để tạo được một proxy ta cần có các nguyên liệu đầu vào:
1) tên lớp cần làm proxy,
2) tên lớp sẽ dùng để thêm vào các tính năng bổ sung (decorate)
3) targetObject: là đối tượng khi được tạo bằng proxy sẽ đứng ra gọi các phương thức.

public class MyProxyFactory {

      public T createProxy(Class clazz, final T targetObject,
                  final MyDecorateClass decorate) {
            InvocationHandler invocationHandler = new InvocationHandler() {

                  public Object invoke(Object proxy, Method method, Object[] args)
                              throws Throwable {

                        decorate.before();
                        Object object = method.invoke(targetObject, args);
                        decorate.after();
                        return object;
                  }
            };

            Proxy proxy = (Proxy) Proxy.newProxyInstance(Thread.currentThread()
                        .getContextClassLoader(), new Class[] { clazz },
                        invocationHandler);

            return (T) proxy;
      }


Để tạo một proxy rất đơn giản ta chỉ cần gọi Proxy.newProxyInstance() với các thông số như sau:
-         ClassLoader hiện tại
-         Một mảng các lớp cần làm proxy
-         Và cuối cùng là invocationHandler. Bộ xử lý gọi hàm (invocation handler) làm nhiệm vụ ráp nối phương thức chính (của lớp cần làm proxy) với các phương thức đánh chặn (từ lớp trang trí)

Lớp trang trí chỉ đơn giản là in ra dòng chữ trước và sau khi phương thức chính được thực thi :

public class MyDecorateClass {

      void before()
      {
            System.out.println("I invoke you BEFORE actual doing your task");
      }
      void after()
      {
            System.out.println("I invoke you AFTER actual doing your task");
      }
}


Cài đặt một hàm main để test :
      public static void main(String[] args)
      {          
            MyProxyFactory myProxy = new MyProxyFactory();
            MyDecorateClass myDecorate = new MyDecorateClass();
           
            Animal proxiedDog = myProxy.createProxy(Animal.class, new Dog(), myDecorate);
            String words = proxiedDog.bark();  
           
            System.out.println(words);               
      }

Ta được kết quả như sau :

I invoke you BEFORE actual doing your task
gau gau gau
I invoke you AFTER actual doing your task
gau gau gau

Có nghĩa là TRƯỚC và SAU khi hàm bark() được thực thi, hai phương thức before và after của MyDecorateClass đã được gọi.

Qua ví dụ trên ta thấy rõ ràng, những quảng cáo về sức mạnh của Spring AOP hay AspectJ cũng khá đơn giản và không có một chút gì kỳ bí. Nó hoàn toàn có thể tự được thực hiện bởi những lập trình viên bình thường.

Ví dụ có thể được download ở đây: MyProxy.zip - 6.2 KB  

Saturday, March 5, 2011

Tự xây dựng 1 framework đảo ngược điều khiển (IoC - Inversion of Control)

1. Nguyên lý đảo ngược điều khiển
Sự ra đời của Spring như là một chọn lựa khác cho EJB đã đánh dấu một bước ngoặt trong lịch sử phát triển của Enterprise Java. Theo thời gian Spring không ngừng được mở rộng với ngày càng nhiều tính năng mới. Tuy vậy, một trong những phần chủ chốt của Spring vẫn là IoC container. Đảo ngược điều khiển là một nguyên lý (hay design pattern) có từ trước khi Spring ra đời năm 2002. Nguyên lý này cũng được mô tả trong cuốn sách Design Pattern của Gang of Four với cách nói nhại theo Hollywood: "Don't call us. We'll call you"

Vậy đảo ngược điều khiển là gì? Hãy xét ví dụ sau:
Giả sử ta có interface Animal với phương thức là say() với ngụ ý là  mỗi loài vật sẽ có tiếng kêu khác nhau. Ví dụ bò rống ò ò ò, còn chó thì sủa gâu gâu gâu.


public interface Animal {
void say();
}
public class Cow implements Animal {
public void say() {
System.out.println("Cow say: oo oo oo");
}
}

public class Dog implements Animal {
public void say() {
System.out.println("Dog say: gau gau gau");
}
}

Theo cách truyền thống để tạo một đối tượng ta cần phải dùng lệnh như sau:

Animal animal = new Dog();
animal.say();

Cách làm này có nhược điểm là nếu muốn thay đổi kiểu của animal là Cow thì cần phải viết lại code. 

2. Tự xây dựng 1 framework đảo ngược điều khiển:
Để xây dựng một framework đảo ngược điều khiển, ta dùng Java Reflection để khởi tạo đối tượng động. Trước tiên ta cần đọc file cấu hình để lấy ra thông tin về tên lớp hiện thực cần được dùng. Giả sử ta có file ioc.properties nội dung như sau:

package.name = examples
bean.name = Animal
implementation.name = Cow

Ta có interface Animal và các lớp hiện thực Cow và Dog như mô tả ở phần 1.

Lớp Injector được hiện thực như sau:

public class Injector {

static Animal getInstance(String propertiesFile) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
{
Animal dependency = null;
Properties pros = new Properties();
pros.load(new FileInputStream(propertiesFile));
String packageName = pros.getProperty("package.name");
String beanName = pros.getProperty("bean.name");
String implementationName = pros.getProperty("implementation.name");

dependency = (Animal)Class.forName(packageName+"."+implementationName).newInstance();
return dependency;
}
}

Cài đặt hàm main để test:
public static void main(String[] args) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
{
Properties p = System.getProperties();
String classpath = p.getProperty("java.class.path");
Animal animal = Injector.getInstance(classpath+"/examples/ioc.properties");
animal.say();
}

Chạy thử ta được kết quả như sau:
Cow say: oo oo oo

Download source code ví dụ này tại đây: MyIOC.zip - 6.0 KB





Làm thế nào để tự tạo một ClassLoader

Lập trình khía cạnh là gì?
Sự xuất hiện của ngôn ngữ hướng đối tượng là một cuộc cách mạng trong lĩnh vực phần mềm. Nhiều người đánh giá nếu không có phương pháp phân tích và thiết kế hướng đối tượng, ngành công nghiệp phần mềm sẽ rơi vào khủng hoảng bởi sự phức tạp ngày càng gia tăng trong các hệ thống phần mềm. May mắn thay điều đó đã không xảy ra. Sau hơn 30 năm thống trị kĩ nghệ phần mềm, hướng đối tượng có vẻ đã hụt hơi với những yêu cầu cao hơn của các ứng dụng thực tế. Trong bối cảnh đó, lập trình hướng khía cạnh (Aspect Oriented Programming gọi tắt là AOP) dường như là một cứu cánh, và là một sự bổ sung để duy trì sức mạnh của hướng đối tượng.

Vậy hướng khía cạnh là gì? Một ví dụ rất đơn giản sau đây sẽ giúp độc giả hình dung sơ nét về lập trình hướng khía cạnh. Giả sử ta có lớp Dog với phương thức bark() – sủa – cho in ra dòng chữ gâu gâu gâu hoặc phức tạp hơn vừa in ra  vừa trả về câu này.

public class Dog implements Animal {

      public String bark(){
            System.out.println("gau gau gau");
            return "gau gau gau";
      }
}

Giả sử nếu ta muốn kiểm tra (authorize) xem người dùng hiện hành có được phép thực thi phương thức này không ta cần phải:

public String bark(){

            if(currentUser.isAuthorized())
{
            System.out.println("gau gau gau");
            return "gau gau gau";
}
else
      throws “Invalid Access Exception”  
      }

Rõ ràng, việc kiểm tra quyền không nằm chung logic với bark(). Việc nhúng đoạn code này vào trong bark khiến cho chương trình rối rắm và khó bảo trì sau này. Cách tốt nhất để làm chuyện này là cho phép một cơ chế Đánh chặn (Interception): trước khi phương thức này được gọi việc kiểm quyền của người dùng sẽ được thực thi . Đoạn mã kiểm sẽ được đặt ở đâu đó trong chương trình, tách riêng (decouple) ra khỏi bark().

Java 1.3 cho phép làm được điều này thông qua DynamicProxy. Trên thực tế Spring AOP sử dụng phương pháp này cònAspectJ và JBOSS AOP sử dụng việc pha trộn lớp (class-weaving).

Tự hiện thực 1 AOP framework
Sau đây ta sẽ tự viết code để làm AOP mà không nhờ vào bất cứ một thư viện thứ ba nào (third-party libraries).

Trước tiên ta có 1 lớp tên là MyProxyFactory, một nhà máy chuyên sản xuất các proxy. Lớp này có 1 phương thức duy nhất là createProxy(). Để tạo được một proxy ta cần có các nguyên liệu đầu vào:
1) tên lớp cần làm proxy,
2) tên lớp sẽ dùng để thêm vào các tính năng bổ sung (decorate)
3) targetObject: là đối tượng khi được tạo bằng proxy sẽ đứng ra gọi các phương thức.

public class MyProxyFactory {

      public T createProxy(Class clazz, final T targetObject,
                  final MyDecorateClass decorate) {
            InvocationHandler invocationHandler = new InvocationHandler() {

                  public Object invoke(Object proxy, Method method, Object[] args)
                              throws Throwable {

                        decorate.before();
                        Object object = method.invoke(targetObject, args);
                        decorate.after();
                        return object;
                  }
            };

            Proxy proxy = (Proxy) Proxy.newProxyInstance(Thread.currentThread()
                        .getContextClassLoader(), new Class[] { clazz },
                        invocationHandler);

            return (T) proxy;
      }


Để tạo một proxy rất đơn giản ta chỉ cần gọi Proxy.newProxyInstance() với các thông số như sau:
-         ClassLoader hiện tại
-         Một mảng các lớp cần làm proxy
-         Và cuối cùng là invocationHandler. Bộ xử lý gọi hàm (invocation handler) làm nhiệm vụ ráp nối phương thức chính (của lớp cần làm proxy) với các phương thức đánh chặn (từ lớp trang trí)

Lớp trang trí chỉ đơn giản là in ra dòng chữ trước và sau khi phương thức chính được thực thi :

public class MyDecorateClass {

      void before()
      {
            System.out.println("I invoke you BEFORE actual doing your task");
      }
      void after()
      {
            System.out.println("I invoke you AFTER actual doing your task");
      }
}


Cài đặt một hàm main để test :
      public static void main(String[] args)
      {          
            MyProxyFactory myProxy = new MyProxyFactory();
            MyDecorateClass myDecorate = new MyDecorateClass();
           
            Animal proxiedDog = myProxy.createProxy(Animal.class, new Dog(), myDecorate);
            String words = proxiedDog.bark();  
           
            System.out.println(words);               
      }

Ta được kết quả như sau :

I invoke you BEFORE actual doing your task
gau gau gau
I invoke you AFTER actual doing your task
gau gau gau

Có nghĩa là TRƯỚC và SAU khi hàm bark() được thực thi, hai phương thức before và after của MyDecorateClass đã được gọi.

Qua ví dụ trên ta thấy rõ ràng, những quảng cáo về sức mạnh của Spring AOP hay AspectJ cũng khá đơn giản và không có một chút gì kỳ bí. Nó hoàn toàn có thể tự được thực hiện bởi những lập trình viên bình thường.


1. ClassLoader là gì?

ClassLoader như tên gọi của nó là một thành phần của Máy Ảo Java được dùng để load các class java vào trong máy ảo để thực thi. Với cách hiểu như vậy, ta hoàn toàn có thể dự đoán cách thức mà ClassLoader hoạt động.

Để chạy 1 ứng dụng Java ta cần có các thư viện của Java là những file jar chứa trong jre/lib. Như vậy những Class chứa trong các thư viện này phải được nạp vào trước thông qua Bootstrap ClassLoader. Sau đó AppClassLoader sẽ nạp các Class của bản thân ứng dụng và các lớp chứa trong những gói jar mà ứng dụng cần đến ví dụ như logging.

Việc nạp một Class sẽ do ClassLoader đảm trách thông qua phương thức loadClass. Do vậy để thực hiện một ClassLoader ta chỉ cần mở rộng lớp này.

Trình tự thực thi của phương thức loadClass như sau:
- Tìm đến thư mục chứa file .class của class cần nạp
- Mở và đọc file trả về nội dung file .class dưới dạng byte[]
- Trả về Class đã nạp bằng cách gọi phương thức defineClass.

*Trước khi nạp một class cần kiểm tra xem class đã được nạp chưa? Nếu class đã được nạp thì hàm loadClass chỉ cần trả về class đã nạp
**Nếu tên class bắt đầu bằng java nghĩa là đây là một class hệ thống, chỉ cần dùng phương thức findSystemClass để trả về class.

2. Ví dụ MyClassLoader
Giả sử ta có một class tên là FooClass. Khi được nạp class này sẽ in ra màn hình dòng chữ Loaded, sau khi được khởi tạo, nó sẽ in ra dòng chữ Created.

public class FooClass {
        static {
          System.out.println ("Loaded");
        }
        public FooClass () {
          System.out.println ("Created");
        }
       
      }

Còn đây là lớpCustomClassLoader mở rộng lớp ClassLoader của Java.

public class CustomClassLoader extends ClassLoader {

      private Hashtable classes = new Hashtable();

      public CustomClassLoader() {
            super(CustomClassLoader.class.getClassLoader());
      }

      public Class loadClass(String className) throws ClassNotFoundException {
            return findClass(className);
      }

      public Class findClass(String className) {
            byte classByte[];

            className = className.trim();
            Class result = null;
            result = (Class) classes.get(className);
            if (result != null) {
                  return result;
            }
            if (className.startsWith("java")) {
                  try {
                        return findSystemClass(className);
                  } catch (Exception e) {
                  }
            }

            try {
                  String classPath = "D:\\java extreme\\classloader\\MyClassLoader\\bin\\";
                  String fileName = className.replace('.', File.separatorChar) + ".class";
                  String fullPathToFile = classPath + fileName;
                  classByte = loadClassData(fullPathToFile);
                  result = defineClass(className, classByte, 0, classByte.length,   null);

                  classes.put(className,result);
                 
                  return result;

            } catch (Exception e) {
                  return null;
            }
      }

      private byte[] loadClassData(String className) throws IOException {
            File f;
            f = new File(className);
            int size = (int) f.length();
            byte buff[] = new byte[size];
            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);
            dis.readFully(buff);
            dis.close();
            return buff;
      }
}

Cài đặt một hàm main để test lớp này:

    public static void main(String [] args) throws Exception{
       CustomClassLoader customClassLoader = new CustomClassLoader();
       Class c = customClassLoader.loadClass("_examples.FooClass_");
      
       Object fc = c.newInstance();
      
       FooClass fc2 = new FooClass();
           
       System.out.println(fc.getClass().getClassLoader());
      
       System.out.println(fc2.getClass().getClassLoader());      
             
    }
Ta được kết quả như sau:
Loaded
Created
Loaded
Created
examples.CustomClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@11b86e7

Chứng tỏ fc được nạp bởi CustomClassLoader

Download ví dụ này ở đây MyClassLoader.zip - 5.9 KB