2015-03-01 15:36

Spring Security 存取控制表示式

常用内建表示式

ps: 定義在 SecurityExpressionRoot

表示式說明
hasRole('role')當前的 User 擁有指定的 Role 就回傳 true
hasAnyRole('role1', 'role2')當前的 User 擁有任一個 Role 就回傳 true
principal當前的 User 的 Principal 物件
authentication當前的 User 的 Authentication 物件
permitAll總是為 true
denyAll總是為 false
isAnonymous()當前的 User 是匿名登入就回傳 true
isRememberMe()當前的 User 是透過 remember-me 登入就回傳 true
isAuthenticated()當前的 User 不是匿名登入就回傳 true
isFullyAuthenticated()當前的 User 不是匿名登入或 remember-me 登入就回傳 true



在方法執行前的驗證


驗證 User 角色
@PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);

驗證參數值是否等於 User 名稱
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

驗證 User 角色以及來源 IP 區間
@PreAuthorize("hasRole('admin') and hasIpAddress('192.168.1.0/24')")
public void doSomething(Contact contact);



在方法內的驗證


取得角色驗證
@RequestMapping("/index")
public void index(HttpServletRequest request) {
    System.out.println(request.isUserInRole("ROLE_USER"));

    if (request.isUserInRole("admin")) {
        System.out.println("is admin");
    }
}



在 JSP 的驗證


取得 User 名稱
<sec:authentication property="name"/>
<sec:authentication property="principal.username"/>

取得 User IP
<sec:authentication property="details.remoteAddress"/>

取得 User SessionId
<sec:authentication property="details.sessionId"/>

驗證角色為 admin 才顯示
<sec:authorize access="hasRole('admin')">
    <div>is admin</div>
</sec:authorize>

驗證角色為 admin 存入變數 isAdmin
<sec:authorize var="isAdmin" access="hasRole('admin')" />
<c:if test="isAdmin">
    <div>is admin</div>
</c:if>


參考自:15. Expression-Based Access Control

2015-02-27 22:27

在 Eclipse 為 Tomcat Library 增加原始碼附件

在專案目錄下選擇 [Java Resources]->[Libraries]->[Apache Tomcat]->[xxx.jar]->[右鍵內容]


在 Java 程式碼附件 就可以連接程式原始碼


在 Javadoc 位置 則可以連接 Javadoc



其實剛剛的設定都記錄在:
{WorkSpace}/.metadata/.plugins/org.eclipse.jst.server.core/
org.eclipse.jst.server.tomcat.runtimeTarget.xml


內容如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<classpath>
  <source-attachment entry="D:/apache-tomcat-7.0.53/lib/commons-io-2.4.jar" runtime-id="Apache Tomcat v7.0" source-attachment-path="D:/apache-tomcat-7.0.53/src/commons-io-2.4-sources.jar">
    <attribute name="javadoc_location" value="jar:file:/D:/apache-tomcat-7.0.53/src/commons-io-2.4-javadoc.jar!/"/>
  </source-attachment>
  <source-attachment entry="D:/apache-tomcat-7.0.53/lib/commons-logging-1.1.3.jar" runtime-id="Apache Tomcat v7.0" source-attachment-path="D:/apache-tomcat-7.0.53/src/commons-logging-1.1.3-sources.jar">
    <attribute name="javadoc_location" value="jar:file:/D:/apache-tomcat-7.0.53/src/commons-logging-1.1.3-javadoc.jar!/"/>
  </source-attachment>
</classpath>


所以只要編寫 org.eclipse.jst.server.tomcat.runtimeTarget.xml 的內容,重新啟動 Eclipse 就會連結原始碼附件,當然這麼麻煩的事還是寫程式自動處理比較快樂,既然是用 Eclipse 寫 Java 當然是用 Ant 來處理是最好的。

首先幾個環境定義:
  • Server Src 路徑:D:/apache-tomcat-7.0.53/src
  • Server Lib 路徑:D:/apache-tomcat-7.0.53/lib
  • Runtime Id:Apache Tomcat v7.0
  • Jar 名稱:xxxxx.jar
  • Source 名稱:xxxxx-sources.jar
  • Javadoc 名稱:xxxxx-javadoc.jar


tomcat-xml-build.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE project>
<project name="tomcat-xml-build" default="deploy-output-xml" basedir=".">

    <!-- 載入額外 ant-contrib 定義  [if,not,or,then,equals] -->
    <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

    <property name="tomcat.id" value="Apache Tomcat v7.0" />
    <property name="tomcat.home" value="D:/apache-tomcat-7.0.53" />
    <property name="output.file" value="org.eclipse.jst.server.tomcat.runtimeTarget.xml" />
    <basename property="ant.filename" file="${ant.file}" />


    <path id="tomcat.lib">
        <fileset dir="${tomcat.home}/lib" includes="*.jar" />
    </path>


    <target name="build-xml">
        <echo>====================================================================</echo>
        <echo>開始建立 ${output.file}</echo>

        <echo file="${output.file}"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
<classpath>
<!-- Code Generate from "${ant.filename}" --></echo>

        <foreach target="loop-jar-file" param="jar-file">
            <path refid="tomcat.lib"/>
        </foreach>

        <echo file="${output.file}" append="true">
</classpath></echo>

        <echo>====================================================================</echo>
        <echo>完成產生 ${output.file}</echo>
    </target>


    <target name="loop-jar-file" if="jar-file">
        <echo level="info">${jar-file}</echo>

        <basename property="jar-name" file="${jar-file}" suffix=".jar"/>
        <if>
            <or>
                <available file="${tomcat.home}/src/${jar-name}-sources.jar"/>
                <available file="${tomcat.home}/src/${jar-name}-javadoc.jar"/>
            </or>
            <then>
                <echo>輸出  ${jar-name}</echo>
                <echo file="${output.file}" append="true">
  <source-attachment entry="${tomcat.home}/lib/${jar-name}.jar" runtime-id="${tomcat.id}" source-attachment-path="${tomcat.home}/src/${jar-name}-sources.jar">
    <attribute name="javadoc_location" value="jar:file://${tomcat.home}/src/${jar-name}-javadoc.jar!/"/>
  </source-attachment></echo>
            </then>
        </if>
    </target>



    <target name="deploy-output-xml" depends="build-xml">
        <echo>====================================================================</echo>
        <echo>部署 ${output.file} 到 .metadata</echo>

        <eclipse.convertPath resourcepath="workspace_loc:/" property="workspace_loc"/>
        <copy file="${output.file}" todir="${workspace_loc}/.metadata/.plugins/org.eclipse.jst.server.core" />
    </target>


    <target name="show-properties">
        <echoproperties />
    </target>

</project>


執行前需要 ant-contrib,然後啟動配置必須是[在工作區的同一個 JRE 中執行],這是因為用到 Eclipse Ant 所以提供的環境變數,執行完後重新啟動 Eclipse 就可以看到連結的附件。

2015-02-27 19:57

在 Eclipse 安裝 ant-contrib

先下載 ant-contrib,然後放置到
{eclipse_home}/plugins/org.apache.ant_XXXXXXX/lib

接者在 [視窗]->[喜好設定]->[Ant 執行時期]->[類別路徑]->[Ant 起始目錄項目]->[新增外部 JAR]->[選擇 ant-contrib.jar]


然後在要使用 ant-contrib 的 build.xml 中加入以下宣告:
<taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
2015-02-26 16:15

[轉載] Java 內部類別

轉載自:Java Gossip: 內部類別

可以在類別中再定義類別,稱之為內部類別(Inner class),初學者暫時不會使用到內部類別,在這邊先簡單介紹語法,雖然會無聊一些,不過之後章節就會看到相關應用。

內部類別可以定義在類別區塊之中。例如以下程式片段建立了非靜態的內部類別:

class Some {
    class Other {
    }
}

雖然實務上很少看到接下來的寫法,不過要使用 Some 中的 Other 類別,必須先建立實例 Some 實例。例如:

Some s = new Some();
Some.Other o =  s.new Other();

內部類別也可以使用 public、protected 或 private 宣告。例如:

class Some {
    private class Other {
    }
}

內部類別本身可以存取外部類別的成員,通常非靜態內部類別會宣告為 private,這類內部類別是輔助類別中某些操作而設計,外部不用知道內部類別的存在。

內部類別也可以宣告為 static。例如:

class Some {
    static class Other {
    }
}

一個被宣告為 static 的內部類別,通常是將外部類別當作名稱空間。你可以如下建立類別實例:

Some.Other o = new Some.Other();

被宣告為 static 的內部類別,雖然將外部類別當作名稱空間,但算是個獨立類別,它可以存取外部類別 static 成員,但不可存取外部類別非 static 成員。例如:


方法中也可以宣告類別,這通常是輔助方法中演算之用,方法外無法使用。例如:

class Some {
    public void doSome() {
        class Other {
        }
    }
}

實務上比較少看到在方法中定義具名的內部類別,倒很常看到方法中定義匿名內部類別(Anonymous inner class)並直接實例化,這跟類別繼承或介面實作有關,以下先看一下語法,細節留到談到繼承與介面時再作討論:

Object o = new Object() {
    public String toString() {
        return "無聊的語法示範而已";
    }
};

如果要稍微解釋一下,這個語法定義了一個沒有名稱的類別,它繼承 Object 類別,並重新定義(Override)了 toString() 方法,new 表示實例化這個沒有名稱的類別。匿名類別語法本身,在某些場合有時有些囉嗦,JDK 8 提出了 Lambda,有一部份目的是用來解決匿名類別語法囉嗦的問題,之後會再討論。
2015-02-26 16:07

[轉載] Java 動態代理

轉載自:Java Essence: 動態代理

來看一個最簡單的例子,當您需要在執行某些方法時留下日誌訊息,直覺的,您可能會如下撰寫:
package cc.openhome;

import java.util.logging.*;

public class HelloSpeaker {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    public void hello(String name) {
        // 方法執行開始時留下日誌
        logger.log(Level.INFO, "hello method starts....");
        // 程式主要功能
        System.out.println("Hello, " + name);
        // 方法執行完畢前留下日誌
        logger.log(Level.INFO, "hello method ends....");
    }
}


在 HelloSpeaker 類別中,當執行 hello() 方法時,你希望該方法執行開始與執行完畢時都能留下日誌,最簡單的作法就是如以上的程式設計,在 方法執行的前後加上日誌動作,然而記錄的這幾行程式碼橫切入(Cross-cutting)HelloSpeaker 類別中,對於 HelloSpeaker 來說,日誌的這幾個動作並不屬於 HelloSpeaker 商務邏輯(顯示"Hello"等文字),這使得 HelloSpeaker 增加了額外的職責。

想想如果程式中這種日誌的動作到處都有需求,以上的寫法勢必造成你必須到處撰寫這些日誌動作的程式碼,這將使得維護日誌程式碼的困難度加大。如果需要的服 務(Service)不只有日誌動作,有一些非物件本身職責的相關動作也混入了物件之中(例如權限檢查、交易管理等等),會使得物件的負擔更形加重,甚至 混淆了物件本身該負有的職責,物件本身的職責所佔的程式碼,或許還小於這些與物件職責不相關的動作或服務的程式碼。

另一方面,使用以上的寫法,若你有一日不再需要日誌(或權限檢查、交易管理等)的服務,那麼你將需要修改所有留下日誌動作的程式碼,你無法簡單的就將這些相關服務從即有的程式中移去。

可以使用代理(Proxy)機制來解決這個問題,在這邊討論兩種代理方式:
  • 靜態代理(Static proxy)
  • 動態代理(Dynamic proxy)

在靜態代理的實現中,代理物件與被代理的物件都必須實現同一個介面,在代理物件中可以實現記錄等相關服務,並在需要的時候再呼叫被代理的物件,如此被代理物件當中就可以僅保留商務相關職責。

舉個實際的例子來說,首先定義一個 IHello 介面:
IHello.java
package cc.openhome;

public interface IHello {
    public void hello(String name);
}


然後讓實現商務邏輯的 HelloSpeaker 類別要實現 IHello 介面,例如:
HelloSpeaker.java
package cc.openhome;

public class HelloSpeaker implements IHello {
    public void hello(String name) {
        System.out.println("Hello, " + name);
    }
}


可以看到,在 HelloSpeaker 類別中現在沒有任何日誌的程式碼插入其中,日誌服務的實現將被放至代理物件之中,代理物件同樣也要實現 IHello 介面,例如:
HelloProxy.java
package cc.openhome;

import java.util.logging.*;

public class HelloProxy implements IHello {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    private IHello helloObject;

    public HelloProxy(IHello helloObject) {
        this.helloObject = helloObject;
    }

    public void hello(String name) {
        // 日誌服務
        log("hello method starts....");

        // 執行商務邏輯
        helloObject.hello(name);

        // 日誌服務
        log("hello method ends....");
    }

    private void log(String msg) {
        logger.log(Level.INFO, msg);
    }
}


在 HelloProxy 類別的 hello() 方法中,真正實現商務邏輯前後可以安排記錄服務,可以實際撰寫一個測試程式來看看如何使用代理物件。
ProxyDemo.java
package cc.openhome;

public class ProxyDemo {
    public static void main(String[] args) {
        IHello proxy =
            new HelloProxy(new HelloSpeaker());
        proxy.hello("Justin");
    }
}


程式中呼叫執行的是代理物件,建構代理物件時必須給它一個被代理物件,記得在操作取回的代理物件時,必須轉換操作介面為 IHello 介面。

代理物件 HelloProxy 將代理真正的 HelloSpeaker 來執行 hello(),並在其前後加上日誌的動作,這使得我們的 HelloSpeaker 在撰寫時不必介入日誌動作,HelloSpeaker 可以專心於它的職責。

在 JDK 1.3 之後加入了可協助開發動態代理功能的 API 等相關類別,您不必為特定物件與方法撰寫特定的代理物件,使用動態代理,可以使得一個處理者 (Handler)服務於各個物件,首先,一個處理者的類別設計必須實作 java.lang.reflect.InvocationHandler 介面, 以實例來進行說明,例如設計一個 LogHandler 類別:
LogHandler.java
package cc.openhome;

import java.util.logging.*;
import java.lang.reflect.*;

public class LogHandler implements InvocationHandler {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    private Object delegate;

    public Object bind(Object delegate) {
        this.delegate = delegate;
        return Proxy.newProxyInstance(
            delegate.getClass().getClassLoader(),
            delegate.getClass().getInterfaces(),
            this
        );
    }

    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable 
    {
        Object result = null;

        try {
            log("method starts..." + method);

            result = method.invoke(delegate, args);

            logger.log(Level.INFO, "method ends..." + method);
        } catch (Exception e){
            log(e.toString());
        }

        return result;
    }

    private void log(String message) {
        logger.log(Level.INFO, message);
    }
}


主要的概念是使用 Proxy.newProxyInstance() 靜態方法建立一個代理物件(底層會使用 Native 的方式生成代理物件的 Class 實例),建立代理物件時必須告知所要代理的介面,之後您可以操作所 建立的代理物件,在每次操作時會呼叫 InvocationHandler 的 invoke() 方法,invoke() 方法會傳入被代理物件的方法名稱與執行 參數,實際上要執行的方法交由 method.invoke(),您在 method.invoke() 前後加上記錄動作,method.invoke() 傳 回的物件是實際方法執行過後的回傳結果。

接下來撰寫一個測試的程式,您要使用 LogHandler 的 bind() 方法來綁定被代理物件,如下所示:
ProxyDemo.java
package cc.openhome;

public class ProxyDemo {
    public static void main(String[] args) {
        LogHandler logHandler  = new LogHandler();

        IHello helloProxy =
            (IHello) logHandler.bind(new HelloSpeaker());

        helloProxy.hello("Justin");
    }
}

2015-02-26 15:44

[轉載] Java 型態通配字元

轉載自:Java Essence: 我只收這種東西

如果你定義了以下的類別:
class Node<T> {
    T value;
    Node<T> next;

    Node(T value, Node<T> next) {
        this.value = value;
        this.next = next;
    }
}


如果在以下的例子中:
class Fruit {}
class Apple extends Fruit {
    @Override
    public String toString() {
        return "Apple";
    }
}

class Banana extends Fruit {
    @Override
    public String toString() {
        return "Banana";
    }
}


public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<Fruit> fruit = apple;  // 編譯錯誤,incompatible types
    }
}



在範例中,apple 的型態是 Node<Apple>,而 fruit 的型態為 Node<Fruit>,你將 apple 所參考的物件 給 fruit 參考,那麼 Node<Apple> 該是一種 Node<Fruit> 呢?在上例中編譯器給你的答案為「不是」!

如 果 B 是 A 的子型態,而 Node<B> 被視為一種 Node<A> 型態,則稱 Node 具有共變性(Covariance)或有彈性的(flexible)。如 果 Node<A> 被視為一種 Node<B> 型態,則稱 Node 具有逆變性(Contravariance)。如果不具共變性或逆變性,則 Node 是不可變 的(nonvariant)或嚴謹的(rigid)。

Java 的泛型不支援共變性,不過可以使用型態通配字元 ? 與 extends 來宣告變數,使其達到類似共變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<? extends Fruit> fruit = apple; // 類似共變性效果
    }
}


一個實際應用的例子是:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
        Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
        Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);

        Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
        Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);

        show(apple3);
        show(banana2);
    }

    static void show(Node<? extends Fruit> n) {
        Node<? extends Fruit> node = n;
        do {
            System.out.println(node.value);
            node = node.next;
        } while(node != null);
    }
}



你的目的是可以顯示所有的水果節點,由於 show() 方法使用型態通配字元宣告參數,使得 n 具備類似共變性的效果,因此 show() 方法就可以顯示 Node<Apple> 也可以顯示 Node<Banana>。

Java 的泛型亦不支援逆變性,不過可以使用型態通配字元 ? 與 super 來宣告變數,使其達到類似逆變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Fruit> fruit = new Node<Fruit>(new Fruit(), null);
        Node<? super Apple> apple = fruit;
        Node<? super Banana> banana = fruit;
    }
}


一個實際應用的例子如下:
class Fruit {
    int price;
    int weight;
    Fruit(int price, int weight) {
        this.price = price;
        this.weight = weight;
    }
}

class Apple extends Fruit {
     Apple(int price, int weight) {
         super(price, weight);
     }
}

class Banana extends Fruit {
     Banana(int price, int weight) {
         super(price, weight);
     }
}

interface Comparator<T> {
    int compare(T t1, T t2);
}

class Basket<T> {
    private T[] things;
    Basket(T... things) {
        this.things = things;
    }
    void sort(Comparator<? super T> comparator) {
        // 作一些排序
    }
}


籃子(Basket)中可以置放各種物品,並可以傳入一個比較器(Comparator)進行排序。假設你分別在兩個籃子中放置了蘋果(Apple)與香蕉(Banana):
public class Main {
    public static void main(String[] args) {
        Comparator<Fruit> comparator = new Comparator<Fruit>() {
            public int compare(Fruit f1, Fruit f2) {
                return f1.price - f2.price;
            }
        };
        Basket<Apple> b1 = new Basket<Apple>(
            new Apple(20, 100), new Apple(25, 150)
        );
        Basket<Banana> b2 = new Basket<Banana>(
            new Banana(30, 200), new Banana(25, 250)
        );
        b1.sort(comparator);
        b2.sort(comparator);
    }
}


現在 b1 的型態為 Basket<Apple>,而 b2 的型態為 Basket<Banana>,你可以如上實作一個水果(Fruit)比較器,比較水果的價格進行排序,這可以同時適用於 Basket<Apple> 與 Basket<Banana>。
2015-02-26 14:40

[PHP][Java][C#] 用 XSD 驗證 XML

menu_config.xml 要驗證的 XML
<?xml version="1.0" encoding="utf-8"?>
<menu_config>
    <menu title="文章管理" url="~/Article" target="" allow="">
        <submenu title="列表" url="~/Article/list" target="" allow=""/>
        <submenu/>
        <submenu title="新增" url="~/Article/add" target="" allow=""/>
    </menu>
    <menu/>
    <menu title="帳號管理" url="~/Admin"/>
</menu_config>


menu_config.xsd 結構定義
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

    <xs:element name="menu_config">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="menu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="menuType">
        <xs:sequence>
            <xs:element name="submenu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
        <xs:attribute name="title" type="xs:string"/>
        <xs:attribute name="url" type="xs:string"/>
        <xs:attribute name="target" type="xs:string"/>
        <xs:attribute name="allow" type="xs:string"/>
    </xs:complexType>

</xs:schema>
element minOccurs
最少的出現次數,不設置為至少出現 1 次,設置 0 為可有可無。
element maxOccurs
最大的出現次數,設置 unbounded 為無上限。
attribute type
型別必須為 QName,常用的有 xs:string, xs:date, xs:int, xs:integer, xs:decimal, xs:boolean, xs:double, xs:float。


PHP
$xmlFile = 'menu_config.xml';
$xsdFile = 'menu_config.xsd';

/* 啟用自行錯誤處裡 */
libxml_use_internal_errors(true);

$xml = new DOMDocument();
$xml->load($xmlFile);

if (!$xml->schemaValidate($xsdFile)) {
    /* 顯示錯誤訊息 */
    print_r(libxml_get_errors());
    libxml_clear_errors();
}


C#
var menuConfig = XDocument.Load("menu_config.xml");
schemas.Add("", XmlReader.Create("menu_config.xsd"));

menuConfig.Validate(schemas, (o, e) => {
    e.Message.Dump();
});


Java
import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;


public class TestXsd {

    public static void main(String[] args) {
        File xsd = new File("menu_config.xsd");
        File xml = new File("menu_config.xml");

        try {
            SchemaFactory factory = SchemaFactory.newInstance(
                XMLConstants.W3C_XML_SCHEMA_NS_URI
            );
            Schema schema = factory.newSchema(xsd);
            Validator validator = schema.newValidator();

            validator.validate(new StreamSource(xml));
            System.out.println("xml is valid");
        }
        catch (SAXException|IOException e) {
            System.out.println("Reason: " + e.getLocalizedMessage());
        }
    }
}



參考資料:
XML 結構描述項目 - MSDN
XML Schema Tutorial W3Schools
2015-02-26 13:16

[Java] 列出 HttpServletRequest header

Enumeration<String> names = request.getHeaderNames();

for (String name : Collections.list(names)) {
    System.out.println(name + " : " + request.getHeader(name));
}