Ubuntu 設定 Apache + Tomcat + SSL 憑證

因為開發的網站牽涉到金流問題,因此必須使用https安全通道來傳送資料,原本主機上就有Apache Web Server走80 port,以及Apacha Tomcat走8080 port,但是當一遇到要走https後狀況就變複雜了,因為在Apache Web Server是443 port,而Tomcat是8443 port,原本使用ProxyPass將http 80 port轉到Tomcat 8080 port的方法似乎不能同理用在https上。解決方式是必須透過Apache VirtualHost的SSL設定,並使用ajp的方式轉換到Tomcat上。

1.安裝 OpenSSL
sudo apt-get install openssl

2.啟用SSL
sudo a2enmod ssl
(如果要停用 sudo a2dismod ssl)

3.安裝憑證
這一步驟先教大家產生自己的私人憑證,壞處是當你用https連線時,瀏覽器會出現警告訊息,這是因為憑證沒有經過第三方公司認證過,最好的作法就是去購買一個已經認證過的憑證或是使用StartSSL的免費憑證服務(https://www.startssl.com/),這部份可以參考(http://blog.mowd.tw/index.php?pl=950)
3.1.建立憑證目錄
sudo mkdir /etc/apache2/ssl
3.2.產生憑證
openssl req -new -x509 -days 365 -nodes -out /etc/apache2/ssl/apache.pem -keyout /etc/apache2/ssl/apache.key
(-days 後面的參數365是憑證的有效日期,可以依需要自行調整,-out 和 –keyout 是憑證和金鑰產出後要存放的路徑。)

4.修改虛擬主機設定
這一段是原本80 port透過ajp轉到tomcat的設定
<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass / ajp://localhost:8009/
ProxyPassReverse / ajp://localhost:8009/
ServerName 127.0.0.1
</VirtualHost>


這一段則是將https轉到tomcat的https且不用轉到tomcat:8443的port
<VirtualHost *:443>
SSLEngine on
#使用自己產生的憑證
SSLCertificateFile /etc/apache2/ssl/apache.pem
SSLCertificateKeyFile /etc/apache2/ssl/apache.key
#以下設定是使用StartSSL產生的檔案用
#SSLCertificateFile /etc/apache2/ssl/ssl.crt
#SSLCertificateKeyFile /etc/apache2/ssl/ssl.key
#SSLCertificateChainFile /etc/apache2/ssl/sub.class1.server.ca.pem
#SSLCACertificateFile /etc/apache2/ssl/ca.pem

ProxyPass / ajp://localhost:8009/
ProxyPassReverse / ajp://localhost:8009/
ServerName 127.0.0.1

</VirtualHost>

如此一來就可以同時使用http與https透過apache來轉址到tomcat上
5.重新啟動apache

[Grails]在Tomcat環境下使用ssl憑證配置

為了網站的訊息傳輸安全性,尤其是當涉及金流、線上刷卡的服務時,一般網站會使用加密的HTTP(也就是HTTPS)來傳輸資料,如此一來就可以防止中間人竊取資料,這篇文章簡單的介紹一下在Grails+Tomcat+Linux環境下,如何建立設定HTTPS的環境。

主要步驟如下:
1.產生憑證金鑰(.keystore)
2.複製金鑰到Tomcat目錄
3.設定Tomcat的server.xml
4.修改Grails應用程式的web.xml設定

以下詳細描述各步驟

1.產生憑證金鑰(.keystore)

因為是要給Tomcat使用的金鑰,所以這裡就使用Java內建的keytool工具
在命令列下輸入以下指令keytool -genkey”
然後跟著指示依序輸入以下資料:
輸入 keystore 密碼:
重新輸入新密碼:
您的名字與姓氏為何?
  [Unknown]:
您的編制單位名稱為何?
  [Unknown]:  
您的組織名稱為何?
  [Unknown]:
您所在的城市或地區名稱為何?
  [Unknown]: 
您所在的州及省份名稱為何?
  [Unknown]:  
該單位的二字國碼為何
  [Unknown]:
CN=Test, OU=Unknown , O=Unknown , L=Hsinchu, ST=Taiwan, C=Unknown 正確嗎?
  [否]:  Y

輸入 <mykey> 的主密碼
        (RETURN 如果和 keystore 密碼相同):
重新輸入新密碼:

其中比較重要的是密碼部分,這個密碼會在後面Tomcat設定時用到

2.複製金鑰到Tomcat目錄

完成上述步驟後,會產生.keystore檔案,將該檔案複製到tomcat/webapps目錄下

3.設定Tomcat的server.xml

開啟tomcatconfigserver.xml,將以下這段程式碼打開,並且加上keystoreFile與keystorePass兩個屬性後儲存即可,需要重新啟動tomcat才可以讓設定生效

<Connector port="8443″ protocol="HTTP/1.1″ SSLEnabled="true"
 maxThreads="150″  scheme="https" secure="true"
 clientAuth="false" sslProtocol="TLS" keystoreFile="webapps/.keystore"
 keystorePass="[密碼]" />

4.修改Grails應用程式的web.xml設定

要在Grails專案裡面設定web.xml,需先要執行grails install-templates指令,將web.xml取出後,手動設定安全連線的部份,執行指令後會在/src/templates/war/目錄下產生web.xml檔案,開啟該檔案後,新增以下設定資料:

<security-constraint>
<web-resource-collection>
<http-method>GET</http-method>
<http-method>POST</http-method>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>

之後產生war檔後deploy到tomcat就算完成了。

可以輸入以下網址驗證https是否成功
https://localhost:8443/PROJECT 或是輸入 http://localhost:8080/PROJECT

不過因為憑證是自己建立,所以第一次進到網站的使用者,都會出現以下畫面,必須要按同意繼續,才可以瀏覽網站,為了解決這問題可以透過購買憑證的方式來解決,這部份等試出在在與大家分享。

有興趣者,可以先考慮以下文章

[筆記] SSL 憑證購買記
添加 GoDaddy SSL 证书到你的网站

[Grails]整合Facebook登入機制oAuth

先前介紹了很多Spring Security Core Plugin的用法,在Spring Security Core Plugin的頁面中有提到相當多的Plugin是架構在Spring Security基礎上的,包括Facebook、Twitter、OpenID等。這篇文章將更進階的將Facebook登入與Spring Security Core機制做一個整合。

其實如果網站單純要用某個oAuth的認證方式的話,建議是直接可以參考官方的API直接開發就可以,但是如果跟我一樣需要兼容Spring Security來做登入與頁面權限的管理,用人家開發好的Plugin真的省事很多。

這個套件目前算是還有在更新,不過不知道為什麼評價不是很高,至少有達到我基本的要求就好,細部的不方便就不管了。

在使用這個套件前,務必先安裝好Spring Security Core Plugin,可以參考之前的文章:Grails+Spring Security Core Plugin(Part1) ,裝好以後要做到產生SysUser/SysRole的步驟。

#01#安裝套件
在BuildConfig.groovy裡面加上以下這段

plugins {
        compile “org.grails.plugins:spring-security-facebook:0.11″
}

//說明裡面是0.11版本,但是實際上是0.14.2,我還是用0.11來做

#02#設定Facebook App
https://developers.facebook.com/新增一個App(應用程式)
輸入方便識別的應用程式名稱以及小寫英文的程式名稱空間

選擇以 [Facebook 登入網站]這個項目,設定網站位址

(如果你是在本機開發,可能只有localhost,但是FB並不接受localhost or 127.0.0.1,所以還是要用一個domain name,如果沒有的話隨便設定一個,然後去改你本機的hosts設定,參考這裡)

如果設定完成後,你就可以看到你接下來需要用到的App ID跟App Secret

#03#新增FacebookUser
需要新增一個FacebookUser來儲存該FB的ID與aceesToken,並且跟現有的SpringSecurity User做關連,這裡我以xervice.Member來做例子。

long uid

String accessToken
Date accessTokenExpires
static belongsTo = [user: SysUser]

以上四個屬性是必要的,其中static belongsTo = [user: SysUser]中的SysUser則需看你的SpringSecurity 設定而決定。

class Member {

/* Default (injected) attributes of GORM */

String aboutMe
Date birthday
boolean gender
String email
boolean emailConfirm
String firstName
String lastName
long uid
String accessToken
Date accessTokenExpires
static belongsTo = [user: SysUser]


    static mapping = {
autoTimestamp true
aboutMe type: ‘text’
    }
    
static constraints = {
email (email: true,nullable:true,blank:true)
firstName(blank: true,nullable: true)
lastName(blank: true,nullable: true)
aboutMe(blank: true,nullable: true)
birthday(nullable: true)
uid(unique: true)
    }

}

#04#設定Facebook Plugin
網站上面是建議直接打grails s2-init-facebook //configure Spring Security Facebook
但是好像有點問題,其實直接去改Config.groovy就可以

grails.plugins.springsecurity.facebook.domain.classname=’xervice.Member’
grails.plugins.springsecurity.facebook.appId=’你的APP ID’
grails.plugins.springsecurity.facebook.secret=’你的APP Secret’

#05#設定登入/登出按鈕

因為這個plugin預設幫你新增一個Spring Security User做關連,同時assign ROLE_USER與ROLE_FACEBOOK,因此可以結合Spring Security 的TagLib來做登入登出機制的判斷。
<sec:ifNotGranted roles="ROLE_USER">
  <facebookAuth:connect />
</sec:ifNotGranted>
<sec:ifAllGranted roles="ROLE_USER">
  Welcome <sec:username/>! (<g:link uri="/j_spring_security_logout">Logout</g:link>)
</sec:ifAllGranted>

如此一來就大功告成了,不過離實際的需求可能還是有點出入,還好這個plugin保留不少客製化的空間可以修改。

#06#客製化-設定存取其他資料
這個plugin預設只會去存取該使用者在Facebook的user id,如果需要存取其他資料,則需要在Config.groovy設定

grails.plugins.springsecurity.facebook.permissions = ’email,user_about_me,user_birthday’

其他設定可以參考這裡
https://developers.facebook.com/docs/reference/login/#permissions
http://splix.github.io/grails-spring-security-facebook/guide/single.html#3.1%20Basic%20Usage

#07#客製化-使用FacebookAuthService
這個plugin預設是會在第一次登入時,建立一個SysUser(Spring Security User),然後把username設定為"facebook_xxxxxxx" xxxxxx代表facebook user id,同時assign ROLE_USER與ROLE_FACEBOOK,最後再建立一個Member(FacebookUser),預設寫入uid(=facebook user id)、accessToken與accessTokenExpires

我這裡的作法會示範透過API取得該User在Facebook上的email,並且以email當做SysUser.username,並設定Member.email

步驟如下:
在src/groovy新增一個FacebookAuthService.groovy
我這裡改寫afterCreate的method,也就是當使用者第一次登入,並且已經新增完SysUser與Member後,才會執行的method。

class FacebookAuthService {

    static transactional = true
def grailsApplication
    void afterCreate(itri.Member user, FacebookAuthToken token){
def appid = grailsApplication.config.grails.plugins.springsecurity.facebook.appId;
def secret = grailsApplication.config.grails.plugins.springsecurity.facebook.secret;


def url = “https://graph.facebook.com/me?access_token="+user.accessToken; 

def result = [:]
try {
def conn = url.openConnection()
conn.doOutput = true
conn.doInput = true

def paramString = “access_token="+user.accessToken; 

conn.outputStream << paramString
result = JSON.parse(conn.inputStream.text)
} catch (Exception ex) {
ex.printStackTrace()
}

if(result.email){
user.email = result.email

user.user.username = result.email
user.user.save(flush: true)
}

}
}

大功告成!!!

其他還有很多可以改寫的Method,可以參考官方說明文件

Grails+Spring Security Core Plugin(Part3)

上次在Part1中,我們設定好了Spring Security,也提到了Spring Security可以有三種方式,Annotation、Static URL、Dynamic Request Maps,而在Part2裡面我們則是新增好對應的User、Role並且可以正常登入。接下較要把Security真正套用在Project上面,我一開始使用的是這篇文章裡面提到的Static URL的方式,作法如下:

修改設定檔Config.groovy
加上以下程式

1.設定securityConfigType =InterceptUrlMap
grails.plugins.springsecurity.securityConfigType = SecurityConfigType.InterceptUrlMap

2.設定你的頁面權限,這部份要注意順序,因為Spring Security Core會從第一個開始比對,所以如果你把’/**’放在最前面,後面的就完成失效,所以涵蓋範圍越大的要放在越後面

這裡我們設定/login/*是不需要登入,也就是使用IS_AUTHENTICATED_ANONYMOUSLY這個屬性
而其他頁面則需要登入(可以使用RemeberMe)。

grails.plugins.springsecurity.interceptUrlMap = [
   ‘/login/*’:         [‘IS_AUTHENTICATED_ANONYMOUSLY’],
   ‘/**’:               [‘IS_AUTHENTICATED_REMEMBERED’]
]

這樣一來,代表系統除了Login外所有的頁面,都必須要登入過後才可以存取

如果你還要寫一個使用者可以註冊的介面,你是寫在userController的rigister與register_save,就再login後面加上

 ‘/user/register*’:         [‘IS_AUTHENTICATED_ANONYMOUSLY’],

如果其他user的新增、刪除、修改功能要限制管理者才可以做的話,就加上

‘/user/*’:         [‘ROLE_ADMIN’],

這段就務必要放在 ‘/user/register*’:  設定的面,否則使用者永遠到不了註冊頁面

最後,我們在整個網站上的右上角加上登入/登出以及註冊的連結,整個登入的機制就很完整了

開啟views/layouts/main.gsp
找到<div align=right id="globalAction">這行
加上以下程式碼

<sec:ifNotLoggedIn>
<g:link controller="login" action="auth"><g:message code="default.login.label"/></g:link>&nbsp;&nbsp;&nbsp;
<g:link controller="user" action="register"><g:message code="default.register.label"/></g:link>
</sec:ifNotLoggedIn>
<sec:ifLoggedIn>
<sec:username /> (<g:link controller="logout"><g:message code="default.logout.label"/></g:link>)
</sec:ifLoggedIn>
(default.login.label、default.register.label、default.logout.label要自己加入多國語系檔唷)

這樣如果你尚未登入,就會顯示登入與註冊連結;當你已經登 入 ,則會顯示你的登入名稱與登出連結,如圖

Grails+Spring Security Core Plugin(Part2)

上次在Part1的設定中,已經可以成功安裝Spring Security在Grails專案內,接下來可以新增預設的admin使用者來測試看看是否有效。

我們可以在confBootStrap.groovy裡面建立系統啟動時預設跑的程式碼
(雖然使用Grails的Eclipse開發環境可以動態的更新Server程式碼,但是這部份還是必須關掉Server重新啟動材可以生效)
在Bootstrap的init()裡面加下以下程式碼
def userRole = SysRole.findByAuthority('ROLE_USER') ?: new SysRole(authority: 'ROLE_USER').save(failOnError: true)
def adminRole = SysRole.findByAuthority('ROLE_ADMIN') ?: new SysRole(authority: 'ROLE_ADMIN').save(failOnError: true)

以上是檢查ROLE_USER以及ROLE_ADMIN這兩個角色是否存在,不存在則自動加上去
需注意,程式碼內的SysRole與SysUser需根據Part1的設定而有不同。

接下來新增admin這個使用者,並且設定為ROLE_ADMIN
def adminUser = SysUser.findByUsername('admin') ?: new SysUser(
username: 'admin',
password: springSecurityService.encodePassword('admin'),
enabled: true).save(failOnError: true)

if (!adminUser.authorities.contains(adminRole)) {
SysUserSysRole.create adminUser, adminRole
}

如果是使用Spring Security1.2版以後的版本,會發生無法登入的狀況

Sorry, we were not able to find a user with that username and password.


主要是因為password在1.2版時function內部就會自動編碼,
因此不用額外加上springSecurityService.encodePassword('admin')
將這部分直接修改為'admin'後,就可以順利用admin/admin登入了。


Grails+Spring Security Core Plugin(Part1)

最近開始練習用Grails開發專案,除了MVC的功能外,最需要的就是Security的部份,也就是使用者登入、角色權限等功能不用自己去寫,而是套用現有開發很完整的Security Framework。好處除了不用自己開發使用者登入、密碼編碼、角色權限設計等繁瑣工作外,完整的framework通常還可以根據網址、頁面設定不同的權限,穩定度也比自己開發好。

以往用Appfuse開發時,內建是使用Acegi,在Grails裡也有支援Acegi的Plugin,但是評價最高的是Spring Security Core Plugin,因此這次就先改用Spring Security Core Plugin看看。

1.安裝Spring Security Core Plugin
在Grails裡面安裝套件非常簡單,指需要在打入以下指令即可

grails install-plugin spring-security-core

安裝完以後,就要開始設定對應的User/Role

2.設定User/Role類別
接下來要為SpringSecurity建立相對應的User(使用者)、Role(角色)以及UserRole(使用者角色對應)的類別,這部份沒有自動產生是考量到與現有系統使用者的整合,假設你原本系統就有設計User/Role/UserRole類別,則Spring Security需要改用其他名稱。

但是如果兩個User希望整合在一起,官網上建議是以Spring Security的User為父類別,透過繼承的方式來建立自己需要的User類別,這樣在未來Spring Security有變動時,才不會影響到現有程式碼。

建立的指令如下

grails s2-quickstart org.example SysUser SysRole

org.example是你希望的package
SysUser,SysRole則是你定義的User/Role類別名稱,系統會自動在產生SysUserSysRole的類別

程式會自動產生以下檔案

domain classes:
org.example.SecUser
org.example.SecRole
org.example.SecUserSecRole


controllers:
LoginController
LogoutController


views:
login*.*

到UrlMappings.groovy裡面設定login/logout url該導向哪個action,加上下面這段:

“/login/$action?"(controller: “login")
“/logout/$action?"(controller: “logout")

預設的登入畫面

接下來就可以針對頁面設定權限了





3.設定頁面權限
描述角色的方式有兩種
a.資料庫內的Role
b.系統預設的Role
其中資料庫內的Role視專案需求而定,系統預設的包過以下三種
[IS_AUTHENTICATED_ANONYMOUSLY]
任何人都可以存取,不需要登入系統

[IS_AUTHENTICATED_REMEMBERED]
必須為登入的使用者,曾經登入過,且選擇remember me的使用者亦可。通常用來限制使用較不危害系統資料的功能(如檢視)。

[IS_AUTHENTICATED_FULLY]
使用者必須確認是登入過的,即使使用remember me的使用者,也必須重新登入。通常用來限制較嚴謹得功能(如發佈資訊)。

設定頁面權限的方式有三種
a.Annotations
Annotation提供我們可以在controller內自定每一個action的權限,只要在該action上面加上@Secured的annotation tag就可以,語法如下:

@Secured([‘ROLE_USER’])
def index() {…. }

@Secured([‘ROLE_USER’, ‘IS_AUTHENTICATED_FULLY’])

def index() {…. }

b.Static URL rules
修改設定檔Config.groovy,可以定義全局網址的權限設定

grails.plugins.springsecurity.securityConfigType = SecurityConfigType.InterceptUrlMap
grails.plugins.springsecurity.interceptUrlMap = [
    ‘/timeline’:         [‘ROLE_USER’],
    ‘/person/*’:         [‘IS_AUTHENTICATED_REMEMBERED’],
    ‘/post/followAjax’:  [‘ROLE_USER’],
    ‘/post/addPostAjax’: [‘ROLE_USER’, ‘IS_AUTHENTICATED_FULLY’],
    ‘/**’:               [‘IS_AUTHENTICATED_ANONYMOUSLY’]
]

比對權限方式是有照順序的,因此如果第一個邏輯成立,則不再比對其他。例如上述例子的最後一項指的是不需要登入即可存取網站,如果擺在第一項,則其他權限就沒有作用了,需要謹慎使用。

c.Dynamic request maps
此方法是當你有動態頁面權限需求時,可以把設定的內容寫入資料庫內,這時候你或得到的是彈性,但是也失去了些許的系統效能。使用方法如下:

修改設定檔Config.groovy
grails.plugins.springsecurity.securityConfigType = SecurityConfigType.Requestmap

建立一個Requestmap的class

在Bootstrap.groovy建立載入的資料

new Requestmap(url: ‘/timeline’, configAttribute: ‘ROLE_USER’).save()
new Requestmap(url: ‘/person/*’, configAttribute: ‘IS_AUTHENTICATED_REMEMBERED’).save()
new Requestmap(url: ‘/post/followAjax’, configAttribute: ‘ROLE_USER’).save()
new Requestmap(url: ‘/post/addPostAjax’, configAttribute: ‘ROLE_USER,IS_AUTHENTICATED_FULLY’).save()
new Requestmap(url: ‘/**’, configAttribute: ‘IS_AUTHENTICATED_ANONYMOUSLY’).save()

更進階的使用,可以參考