Commits

明点软件 committed 94c4fc8

初始化导入

Comments (0)

Files changed (38)

+syntax: glob
+.classpath
+.project
+.settings/**
+target/**
+web-app/WEB-INF/classes/**

SpringSecurityTaobaoGrailsPlugin.groovy

+import java.util.List
+
+import org.codehaus.groovy.grails.plugins.springsecurity.SecurityFilterPosition
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+import com.mingidea.security.taobao.TaobaoAuthenticationProcessingFilter
+import com.mingidea.security.taobao.TaobaoAuthenticationProvider
+import com.mingidea.security.taobao.TaobaoAuthenticationUserDetailsService
+
+class SpringSecurityTaobaoGrailsPlugin {
+    // the plugin version
+    def version = "0.1"
+    // the version or versions of Grails the plugin is designed for
+    def grailsVersion = "1.3.7 > *"
+    // the other plugins this plugin depends on
+    def dependsOn = [springSecurityCore: '1.1.2 > *']
+    // resources that are excluded from plugin packaging
+    List pluginExcludes = [
+        'grails-app/domain/**',
+        'grails-app/controller/**',
+        'docs/**',
+        'src/docs/**'
+    ]
+
+    def author = "Simon Leung"
+    def authorEmail = "simon.leung@mingidea.com"
+    def title = "Taobao open authentication support for the Spring Security plugin."
+    def description = "Taobao open authentication support for the Spring Security plugin."
+
+    // URL to the plugin's documentation
+    def documentation = "http://grails.org/plugin/spring-security-taobao"
+
+    def doWithSpring = {
+        def conf = SpringSecurityUtils.securityConfig
+        if (!conf || !conf.active) {
+            return
+        }
+
+        SpringSecurityUtils.loadSecondaryConfig 'DefaultTaobaoSecurityConfig'
+        // have to get again after overlaying DefaultTaobaoSecurityConfig
+        conf = SpringSecurityUtils.securityConfig
+
+        if (!conf.taobao.active) {
+            return
+        }
+
+        println 'Configuring Spring Security Taobao ...'
+
+        SpringSecurityUtils.registerProvider 'taobaoAuthenticationProvider'
+        SpringSecurityUtils.registerFilter 'taobaoAuthenticationFilter', SecurityFilterPosition.OPENID_FILTER
+
+        
+        taobaoAuthenticationProvider(TaobaoAuthenticationProvider) {
+            appSecretMap = conf.taobao.appSecretMap
+            authenticationUserDetailsService = ref(authenticationUserDetailsService)
+        }
+        
+        taobaoAuthenticationFilter(TaobaoAuthenticationProcessingFilter){
+            authenticationManager = ref('authenticationManager')
+            sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
+        }
+        
+
+        // custom subclass that searches by username and taobaoAccount
+        authenticationUserDetailsService(TaobaoAuthenticationUserDetailsService) { grailsApplication = ref('grailsApplication') }
+    }
+
+    def doWithApplicationContext = { applicationContext ->
+        def conf = SpringSecurityUtils.securityConfig
+        if (!conf || !conf.active) {
+            return
+        }
+
+        String userClassName = conf.userLookup.userDomainClassName
+        def userClass = applicationContext.grailsApplication.getClassForName(userClassName)
+        String taobaoAccountsPropertyName = conf.taobao.userLookup.accountsPropertyName
+        if (userClass && taobaoAccountsPropertyName && !userClass.newInstance().hasProperty(taobaoAccountsPropertyName)) {
+            println """
+ERROR: Your configuration specifies
+
+   grails.plugins.springsecurity.taobao.userLookup.taobaoAccountsPropertyName='${taobaoAccountsPropertyName}'
+
+for $conf.userLookup.userDomainClassName but there's no property with that name in your user class;
+either add a hasMany for the OpenID strings:
+
+   static hasMany = [${taobaoAccountsPropertyName}: TaobaoAccount]
+
+or set the property to null in Config.groovy if you aren't supporting associating taobaoAccounts with local accounts.
+"""
+
+            // reset the property in case the user doesn't restart to avoid ugly exceptions
+            conf.openid.userLookup.taobaoAccountsPropertyName = ''
+        }
+    }
+}

application.properties

+#Grails Metadata file
+#Tue May 17 14:52:50 CST 2011
+app.grails.version=1.3.7
+app.name=spring-security-taobao
+plugins.hibernate=1.3.7
+plugins.tomcat=1.3.7
+plugins.spring-security-core=1.1.2

grails-app/conf/BootStrap.groovy

+import test.Role
+import test.TaobaoAccount
+import test.User
+import test.UserRole
+
+class BootStrap {
+
+	def springSecurityService
+
+	def init = { servletContext ->
+		String password = springSecurityService.encodePassword('password')
+
+		def roleAdmin = new Role(authority: 'ROLE_ADMIN').save()
+		def roleUser = new Role(authority: 'ROLE_USER').save()
+
+		def user = new User(username: 'user', password: password, enabled: true).save()
+		def admin = new User(username: 'admin', password: password, enabled: true).save()
+
+        def taobaoAccount = new TaobaoAccount(nick: 'l95566505', user: user)
+        user.addToTaobaoAccounts(taobaoAccount)
+        user.save()
+        
+        
+		UserRole.create user, roleUser
+		UserRole.create admin, roleUser
+		UserRole.create admin, roleAdmin, true
+	}
+}

grails-app/conf/BuildConfig.groovy

+grails.project.class.dir = "target/classes"
+grails.project.test.class.dir = "target/test-classes"
+grails.project.test.reports.dir = "target/test-reports"
+//grails.project.war.file = "target/${appName}-${appVersion}.war"
+grails.project.dependency.resolution = {
+    // inherit Grails' default dependencies
+    inherits("global") {
+        excludes 'commons-codec:commons-codec:1.4'
+    }
+    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+    repositories {
+        grailsPlugins()
+        grailsHome()
+        grailsCentral()
+
+        ebr() // SpringSource  http://www.springsource.com/repository
+        // uncomment the below to enable remote dependency resolution
+        // from public Maven repositories
+        mavenLocal()
+        mavenCentral()
+        mavenRepo "http://snapshots.repository.codehaus.org"
+        mavenRepo "http://repository.codehaus.org"
+        mavenRepo "http://download.java.net/maven/2/"
+        mavenRepo "http://repository.jboss.com/maven2/"
+    }
+    dependencies {
+        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
+        // runtime 'mysql:mysql-connector-java:5.1.13'
+        runtime('commons-codec:commons-codec:1.5'){
+            transitive:false
+        }
+        runtime ':spring-security-taobao:0.1'
+    }
+}

grails-app/conf/Config.groovy

+import grails.plugins.springsecurity.SecurityConfigType
+
+// configuration for plugin testing - will not be included in the plugin zip
+ 
+log4j = {
+    // Example of changing the log pattern for the default console
+    // appender:
+    //
+    
+    root {
+        info 'stdout', 'file'
+    }
+    appenders {
+        console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
+    }
+
+    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers
+           'org.codehaus.groovy.grails.web.pages', //  GSP
+           'org.codehaus.groovy.grails.web.sitemesh', //  layouts
+           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
+           'org.codehaus.groovy.grails.web.mapping', // URL mapping
+           'org.codehaus.groovy.grails.commons', // core / classloading
+           'org.codehaus.groovy.grails.plugins', // plugins
+           'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
+           'org.springframework',
+           'org.hibernate',
+           'net.sf.ehcache.hibernate'
+
+    warn   'org.mortbay.log'
+}
+
+grails.plugins.springsecurity.taobao.appSecret = '9f5361aaecfb2cbbf767e2cf9fb92af0'
+
+// Added by the Spring Security Core plugin:
+grails.plugins.springsecurity.userLookup.userDomainClassName = 'test.User'
+grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'test.UserRole'
+grails.plugins.springsecurity.authority.className = 'test.Role'
+grails.plugins.springsecurity.taobao.accountDomainClassName = 'test.TaobaoAccount'
+grails.plugins.springsecurity.taobao.appSecretMap = ['12260155' : '1ccf5dc3f6771ab20e37f8506d14ad68']
+
+grails.plugins.springsecurity.securityConfigType = SecurityConfigType.InterceptUrlMap
+grails.plugins.springsecurity.interceptUrlMap = [
+    '/secure/**':    ['ROLE_USER'],
+    '/admin**':   ['ROLE_ADMIN', 'IS_AUTHENTICATED_FULLY']
+ ]

grails-app/conf/DataSource.groovy

+dataSource {
+    pooled = true
+    driverClassName = "org.hsqldb.jdbcDriver"
+    username = "sa"
+    password = ""
+}
+hibernate {
+    cache.use_second_level_cache = true
+    cache.use_query_cache = true
+    cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
+}
+// environment specific settings
+environments {
+    development {
+        dataSource {
+            dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+            url = "jdbc:hsqldb:mem:devDB"
+        }
+    }
+    test {
+        dataSource {
+            dbCreate = "update"
+            url = "jdbc:hsqldb:mem:testDb"
+        }
+    }
+    production {
+        dataSource {
+            dbCreate = "update"
+            url = "jdbc:hsqldb:file:prodDb;shutdown=true"
+        }
+    }
+}

grails-app/conf/DefaultTaobaoSecurityConfig.groovy

+security {
+    taobao {
+        //appKey -> appSecret
+        appSecretMap = [:]
+        accountDomainClassName = 'TaobaoAccount'
+        active = true
+        //单位:秒
+        authRequestMaxSkewedTime = 1800
+        userLookup {
+            accountsPropertyName = 'taobaoAccounts'
+        }   
+    }
+}

grails-app/conf/UrlMappings.groovy

+class UrlMappings {
+
+	static mappings = {
+		"/$controller/$action?/$id?"{
+			constraints {
+				// apply constraints here
+			}
+		}
+
+		"/"(view:"/index")
+		"500"(view:'/error')
+	}
+}

grails-app/controllers/test/AdminController.groovy

+package test
+
+class AdminController {
+    def index = {
+        render 'admin, need admin role'
+    }
+
+}

grails-app/controllers/test/LoginController.groovy

+package test
+
+import grails.converters.JSON
+
+import javax.servlet.http.HttpServletResponse
+
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+import org.springframework.security.authentication.AccountExpiredException
+import org.springframework.security.authentication.CredentialsExpiredException
+import org.springframework.security.authentication.DisabledException
+import org.springframework.security.authentication.LockedException
+import org.springframework.security.core.context.SecurityContextHolder as SCH
+import org.springframework.security.web.WebAttributes
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+
+class LoginController {
+
+	/**
+	 * Dependency injection for the authenticationTrustResolver.
+	 */
+	def authenticationTrustResolver
+
+	/**
+	 * Dependency injection for the springSecurityService.
+	 */
+	def springSecurityService
+
+	/**
+	 * Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise.
+	 */
+	def index = {
+		if (springSecurityService.isLoggedIn()) {
+			redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
+		}
+		else {
+			redirect action: auth, params: params
+		}
+	}
+
+	/**
+	 * Show the login page.
+	 */
+	def auth = {
+
+		def config = SpringSecurityUtils.securityConfig
+
+		if (springSecurityService.isLoggedIn()) {
+			redirect uri: config.successHandler.defaultTargetUrl
+			return
+		}
+
+		String view = 'auth'
+		String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
+		render view: view, model: [postUrl: postUrl,
+		                           rememberMeParameter: config.rememberMe.parameter]
+	}
+
+	/**
+	 * The redirect action for Ajax requests. 
+	 */
+	def authAjax = {
+		response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
+		response.sendError HttpServletResponse.SC_UNAUTHORIZED
+	}
+
+	/**
+	 * Show denied page.
+	 */
+	def denied = {
+		if (springSecurityService.isLoggedIn() &&
+				authenticationTrustResolver.isRememberMe(SCH.context?.authentication)) {
+			// have cookie but the page is guarded with IS_AUTHENTICATED_FULLY
+			redirect action: full, params: params
+		}
+	}
+
+	/**
+	 * Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page.
+	 */
+	def full = {
+		def config = SpringSecurityUtils.securityConfig
+		render view: 'auth', params: params,
+			model: [hasCookie: authenticationTrustResolver.isRememberMe(SCH.context?.authentication),
+			        postUrl: "${request.contextPath}${config.apf.filterProcessesUrl}"]
+	}
+
+	/**
+	 * Callback after a failed login. Redirects to the auth page with a warning message.
+	 */
+	def authfail = {
+
+		def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
+		String msg = ''
+		def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
+		if (exception) {
+			if (exception instanceof AccountExpiredException) {
+				msg = SpringSecurityUtils.securityConfig.errors.login.expired
+			}
+			else if (exception instanceof CredentialsExpiredException) {
+				msg = SpringSecurityUtils.securityConfig.errors.login.passwordExpired
+			}
+			else if (exception instanceof DisabledException) {
+				msg = SpringSecurityUtils.securityConfig.errors.login.disabled
+			}
+			else if (exception instanceof LockedException) {
+				msg = SpringSecurityUtils.securityConfig.errors.login.locked
+			}
+			else {
+				msg = SpringSecurityUtils.securityConfig.errors.login.fail
+			}
+		}
+
+		if (springSecurityService.isAjax(request)) {
+			render([error: msg] as JSON)
+		}
+		else {
+			flash.message = msg
+			redirect action: auth, params: params
+		}
+	}
+
+	/**
+	 * The Ajax success redirect url.
+	 */
+	def ajaxSuccess = {
+		render([success: true, username: springSecurityService.authentication.name] as JSON)
+	}
+
+	/**
+	 * The Ajax denied redirect url.
+	 */
+	def ajaxDenied = {
+		render([error: 'access denied'] as JSON)
+	}
+}

grails-app/controllers/test/LogoutController.groovy

+package test
+
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+
+class LogoutController {
+
+	/**
+	 * Index action. Redirects to the Spring security logout uri.
+	 */
+	def index = {
+		// TODO  put any pre-logout code here
+		redirect uri: SpringSecurityUtils.securityConfig.logout.filterProcessesUrl // '/j_spring_security_logout'
+	}
+}

grails-app/controllers/test/SecureController.groovy

+package test
+
+class SecureController {
+
+    def index = {
+        render 'secure, need user role'
+    }
+
+}

grails-app/controllers/test/TestController.groovy

+package test
+
+class TestController {
+
+    def index = { render params}
+}

grails-app/domain/test/Role.groovy

+package test
+
+class Role {
+
+	String authority
+
+	static mapping = {
+		cache true
+	}
+
+	static constraints = {
+		authority blank: false, unique: true
+	}
+}

grails-app/domain/test/TaobaoAccount.groovy

+package test
+
+class TaobaoAccount {
+    User owner
+    String nick
+    static belongsTo = User
+}

grails-app/domain/test/User.groovy

+package test
+
+class User {
+
+	String username
+	String password
+	boolean enabled
+	boolean accountExpired
+	boolean accountLocked
+	boolean passwordExpired
+
+    static hasMany = [taobaoAccounts: TaobaoAccount]
+    
+	static constraints = {
+		username blank: false, unique: true
+		password blank: false
+	}
+
+	static mapping = {
+		password column: '`password`'
+	}
+
+	Set<Role> getAuthorities() {
+		UserRole.findAllByUser(this).collect { it.role } as Set
+	}
+}

grails-app/domain/test/UserRole.groovy

+package test
+
+import org.apache.commons.lang.builder.HashCodeBuilder
+
+class UserRole implements Serializable {
+
+	User user
+	Role role
+
+	boolean equals(other) {
+		if (!(other instanceof UserRole)) {
+			return false
+		}
+
+		other.user?.id == user?.id &&
+			other.role?.id == role?.id
+	}
+
+	int hashCode() {
+		def builder = new HashCodeBuilder()
+		if (user) builder.append(user.id)
+		if (role) builder.append(role.id)
+		builder.toHashCode()
+	}
+
+	static UserRole get(long userId, long roleId) {
+		find 'from UserRole where user.id=:userId and role.id=:roleId',
+			[userId: userId, roleId: roleId]
+	}
+
+	static UserRole create(User user, Role role, boolean flush = false) {
+		new UserRole(user: user, role: role).save(flush: flush, insert: true)
+	}
+
+	static boolean remove(User user, Role role, boolean flush = false) {
+		UserRole instance = UserRole.findByUserAndRole(user, role)
+		instance ? instance.delete(flush: flush) : false
+	}
+
+	static void removeAll(User user) {
+		executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user]
+	}
+
+	static void removeAll(Role role) {
+		executeUpdate 'DELETE FROM UserRole WHERE role=:role', [role: role]
+	}
+
+	static mapping = {
+		id composite: ['role', 'user']
+		version false
+	}
+}

grails-app/views/error.gsp

+<html>
+  <head>
+	  <title>Grails Runtime Exception</title>
+	  <style type="text/css">
+	  		.message {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			background-color:#E9E9E9;
+	  		}
+	  		.stack {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			overflow:auto;
+	  			height: 300px;
+	  		}
+	  		.snippet {
+	  			padding: 5px;
+	  			background-color:white;
+	  			border:1px solid black;
+	  			margin:3px;
+	  			font-family:courier;
+	  		}
+	  </style>
+  </head>
+
+  <body>
+    <h1>Grails Runtime Exception</h1>
+    <h2>Error Details</h2>
+
+  	<div class="message">
+		<strong>Error ${request.'javax.servlet.error.status_code'}:</strong> ${request.'javax.servlet.error.message'.encodeAsHTML()}<br/>
+		<strong>Servlet:</strong> ${request.'javax.servlet.error.servlet_name'}<br/>
+		<strong>URI:</strong> ${request.'javax.servlet.error.request_uri'}<br/>
+		<g:if test="${exception}">
+	  		<strong>Exception Message:</strong> ${exception.message?.encodeAsHTML()} <br />
+	  		<strong>Caused by:</strong> ${exception.cause?.message?.encodeAsHTML()} <br />
+	  		<strong>Class:</strong> ${exception.className} <br />
+	  		<strong>At Line:</strong> [${exception.lineNumber}] <br />
+	  		<strong>Code Snippet:</strong><br />
+	  		<div class="snippet">
+	  			<g:each var="cs" in="${exception.codeSnippet}">
+	  				${cs?.encodeAsHTML()}<br />
+	  			</g:each>
+	  		</div>
+		</g:if>
+  	</div>
+	<g:if test="${exception}">
+	    <h2>Stack Trace</h2>
+	    <div class="stack">
+	      <pre><g:each in="${exception.stackTraceLines}">${it.encodeAsHTML()}<br/></g:each></pre>
+	    </div>
+	</g:if>
+  </body>
+</html>

grails-app/views/index.gsp

+<html>
+<head>
+<title>index</title>
+</head>
+<body>
+index
+</body>
+</html>

grails-app/views/login/auth.gsp

+<head>
+<meta name='layout' content='main' />
+<title>Login</title>
+<style type='text/css' media='screen'>
+#login {
+	margin:15px 0px; padding:0px;
+	text-align:center;
+}
+#login .inner {
+	width:260px;
+	margin:0px auto;
+	text-align:left;
+	padding:10px;
+	border-top:1px dashed #499ede;
+	border-bottom:1px dashed #499ede;
+	background-color:#EEF;
+}
+#login .inner .fheader {
+	padding:4px;margin:3px 0px 3px 0;color:#2e3741;font-size:14px;font-weight:bold;
+}
+#login .inner .cssform p {
+	clear: left;
+	margin: 0;
+	padding: 5px 0 8px 0;
+	padding-left: 105px;
+	border-top: 1px dashed gray;
+	margin-bottom: 10px;
+	height: 1%;
+}
+#login .inner .cssform input[type='text'] {
+	width: 120px;
+}
+#login .inner .cssform label {
+	font-weight: bold;
+	float: left;
+	margin-left: -105px;
+	width: 100px;
+}
+#login .inner .login_message {color:red;}
+#login .inner .text_ {width:120px;}
+#login .inner .chk {height:12px;}
+</style>
+</head>
+
+<body>
+	<div id='login'>
+		<div class='inner'>
+			<g:if test='${flash.message}'>
+			<div class='login_message'>${flash.message}</div>
+			</g:if>
+			<div class='fheader'>Please Login..</div>
+			<form action='${postUrl}' method='POST' id='loginForm' class='cssform' autocomplete='off'>
+				<p>
+					<label for='username'>Login ID</label>
+					<input type='text' class='text_' name='j_username' id='username' />
+				</p>
+				<p>
+					<label for='password'>Password</label>
+					<input type='password' class='text_' name='j_password' id='password' />
+				</p>
+				<p>
+					<label for='remember_me'>Remember me</label>
+					<input type='checkbox' class='chk' name='${rememberMeParameter}' id='remember_me'
+					<g:if test='${hasCookie}'>checked='checked'</g:if> />
+				</p>
+				<p>
+					<input type='submit' value='Login' />
+				</p>
+			</form>
+		</div>
+	</div>
+<script type='text/javascript'>
+<!--
+(function(){
+	document.forms['loginForm'].elements['j_username'].focus();
+})();
+// -->
+</script>
+</body>

grails-app/views/login/denied.gsp

+<head>
+<meta name='layout' content='main' />
+<title>Denied</title>
+</head>
+
+<body>
+<div class='body'>
+	<div class='errors'>Sorry, you're not authorized to view this page.</div>
+</div>
+</body>

grails-spring-security-taobao-0.1.zip

Binary file added.

lib/spring-security-taobao-0.1.jar

Binary file added.
+<plugin name='spring-security-taobao' version='0.1' grailsVersion='1.3.7 &gt; *'>
+  <author>Simon Leung</author>
+  <authorEmail>simon.leung@mingidea.com</authorEmail>
+  <title>Taobao open authentication support for the Spring Security plugin.</title>
+  <description>Taobao open authentication support for the Spring Security plugin.</description>
+  <documentation>http://grails.org/plugin/spring-security-taobao</documentation>
+  <resources>
+    <resource>BootStrap</resource>
+    <resource>BuildConfig</resource>
+    <resource>Config</resource>
+    <resource>DataSource</resource>
+    <resource>DefaultTaobaoSecurityConfig</resource>
+    <resource>UrlMappings</resource>
+    <resource>test.AdminController</resource>
+    <resource>test.LoginController</resource>
+    <resource>test.LogoutController</resource>
+    <resource>test.SecureController</resource>
+    <resource>test.TestController</resource>
+    <resource>test.Role</resource>
+    <resource>test.TaobaoAccount</resource>
+    <resource>test.User</resource>
+    <resource>test.UserRole</resource>
+  </resources>
+  <dependencies>
+    <plugin name='springSecurityCore' version='1.1.2 &gt; *' />
+  </dependencies>
+  <behavior />
+</plugin>

scripts/_Install.groovy

+//
+// This script is executed by Grails after plugin was installed to project.
+// This script is a Gant script so you can use all special variables provided
+// by Gant (such as 'baseDir' which points on project base dir). You can
+// use 'ant' to access a global instance of AntBuilder
+//
+// For example you can create directory under project tree:
+//
+//    ant.mkdir(dir:"${basedir}/grails-app/jobs")
+//

scripts/_Uninstall.groovy

+//
+// This script is executed by Grails when the plugin is uninstalled from project.
+// Use this script if you intend to do any additional clean-up on uninstall, but
+// beware of messing up SVN directories!
+//

scripts/_Upgrade.groovy

+//
+// This script is executed by Grails during application upgrade ('grails upgrade'
+// command). This script is a Gant script so you can use all special variables
+// provided by Gant (such as 'baseDir' which points on project base dir). You can
+// use 'ant' to access a global instance of AntBuilder
+//
+// For example you can create directory under project tree:
+//
+//    ant.mkdir(dir:"${basedir}/grails-app/jobs")
+//

src/groovy/com/mingidea/security/taobao/TaobaoAuthenticationUserDetailsService.groovy

+package com.mingidea.security.taobao
+
+import java.util.Collection
+
+import org.codehaus.groovy.grails.commons.GrailsApplication
+import org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService
+import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.GrantedAuthorityImpl
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+
+class TaobaoAuthenticationUserDetailsService extends GormUserDetailsService implements AuthenticationUserDetailsService {
+    GrailsApplication grailsApplication
+
+    @Override
+    public UserDetails loadUserDetails(Authentication auth) throws UsernameNotFoundException {
+        def conf = SpringSecurityUtils.securityConfig
+        def taobaoAccountClassName = conf.taobao.accountDomainClassName
+        Class<?> TaobaoAccount = grailsApplication.getDomainClass(taobaoAccountClassName).clazz
+        
+        String taobaoNick = auth.principal
+        TaobaoAccount.withTransaction { status ->
+            def taobaoAccount = TaobaoAccount.findByNick(taobaoNick)
+            if(!taobaoAccount) {
+                log.info "Taobao account not found: $nick"
+                throw new UsernameNotFoundException('Taobao account not found', nick)
+            }
+            def user = taobaoAccount.owner
+            Collection<GrantedAuthority> authorities = loadAuthorities(user, true)
+            
+            String session = auth.credentials.session
+            
+            return createUserDetails(user, authorities, session, taobaoNick)
+        }
+    }
+    
+    protected Collection<GrantedAuthority> loadAuthorities(user, boolean loadRoles) {
+        if (!loadRoles) {
+            return []
+        }
+
+        def conf = SpringSecurityUtils.securityConfig
+
+        String authoritiesPropertyName = conf.userLookup.authoritiesPropertyName
+        String authorityPropertyName = conf.authority.nameField
+
+        Collection<?> userAuthorities = user."$authoritiesPropertyName"
+        def authorities = userAuthorities.collect { new GrantedAuthorityImpl(it."$authorityPropertyName") }
+        authorities ?: NO_ROLES
+    }
+
+    protected UserDetails createUserDetails(user, Collection<GrantedAuthority> authorities, String session, String taobaoNick) {
+
+        def conf = SpringSecurityUtils.securityConfig
+
+        String usernamePropertyName = conf.userLookup.usernamePropertyName
+        String passwordPropertyName = conf.userLookup.passwordPropertyName
+        String enabledPropertyName = conf.userLookup.enabledPropertyName
+        String accountExpiredPropertyName = conf.userLookup.accountExpiredPropertyName
+        String accountLockedPropertyName = conf.userLookup.accountLockedPropertyName
+        String passwordExpiredPropertyName = conf.userLookup.passwordExpiredPropertyName
+
+        String username = user."$usernamePropertyName"
+        String password = user."$passwordPropertyName"
+        boolean enabled = enabledPropertyName ? user."$enabledPropertyName" : true
+        boolean accountExpired = accountExpiredPropertyName ? user."$accountExpiredPropertyName" : false
+        boolean accountLocked = accountLockedPropertyName ? user."$accountLockedPropertyName" : false
+        boolean passwordExpired = passwordExpiredPropertyName ? user."$passwordExpiredPropertyName" : false
+
+        return new TaobaoUserDetails(username, password, enabled, !accountExpired, !passwordExpired,
+                !accountLocked, authorities, user.id, session, taobaoNick)
+    }
+}

src/groovy/com/mingidea/security/taobao/TaobaoUserDetails.groovy

+package com.mingidea.security.taobao
+
+import java.util.Collection;
+
+import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser;
+import org.springframework.security.core.GrantedAuthority;
+
+class TaobaoUserDetails extends GrailsUser {
+    private String session;
+    private String taobaoNick;
+    public TaobaoUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
+        boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities, Object id,
+        String session, String taobaoNick) {
+        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id);
+        this.session = session
+        this.taobaoNick = taobaoNick
+    }
+    
+    String getSession() {session};
+    String getTaobaoNick() {taobaoNick};  
+}

test/unit/spring/security/taobao/SecureControllerTests.groovy

+package spring.security.taobao
+
+import grails.test.*
+
+class SecureControllerTests extends ControllerUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}

test/unit/test/TestControllerTests.groovy

+package test
+
+import grails.test.*
+
+class TestControllerTests extends ControllerUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}

web-app/WEB-INF/applicationContext.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+	<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
+		<description>Grails application factory bean</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="grailsResourceLoader" ref="grailsResourceLoader" />
+	</bean>
+
+	<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
+		<description>A bean that manages Grails plugins</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="application" ref="grailsApplication" />
+	</bean>
+
+    <bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
+        <constructor-arg>
+            <ref bean="grailsApplication" />
+        </constructor-arg>
+        <property name="pluginManager" ref="pluginManager" />
+    </bean>
+
+    <bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean">
+        <property name="grailsResourceHolder" ref="grailsResourceHolder" />
+    </bean>
+
+    <bean id="grailsResourceHolder" scope="prototype" class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
+        <property name="resources">
+              <value>classpath*:**/grails-app/**/*.groovy</value>
+        </property>
+    </bean>    
+    
+   <bean id="characterEncodingFilter"
+      class="org.springframework.web.filter.CharacterEncodingFilter">
+        <property name="encoding">
+          <value>utf-8</value>
+        </property>
+   </bean>    	
+</beans>

web-app/WEB-INF/sitemesh.xml

+<sitemesh>
+    <page-parsers>
+        <parser content-type="text/html"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=ISO-8859-1"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=UTF-8"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />            
+    </page-parsers>
+
+    <decorator-mappers>
+        <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
+    </decorator-mappers>
+</sitemesh>

web-app/WEB-INF/tld/c.tld

+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 core library</description>
+  <display-name>JSTL core</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>c</short-name>
+  <uri>http://java.sun.com/jsp/jstl/core</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlCoreTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Catches any Throwable that occurs in its body and optionally
+        exposes it.
+    </description>
+    <name>catch</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+exception thrown from a nested action. The type of the
+scoped variable is the type of the exception thrown.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag that establishes a context for
+	mutually exclusive conditional operations, marked by
+	&lt;when&gt; and &lt;otherwise&gt;
+    </description>
+    <name>choose</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag, which evalutes its body if the
+	supplied condition is true and optionally exposes a Boolean
+	scripting variable representing the evaluation of this condition
+    </description>
+    <name>if</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or
+not the body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resulting value of the test condition. The type
+of the scoped variable is Boolean.        
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Retrieves an absolute or relative URL and exposes its contents
+        to either the page, a String in 'var', or a Reader in 'varReader'.
+    </description>
+    <name>import</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ImportTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to import.
+        </description>
+        <name>url</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is Reader.
+        </description>
+        <name>varReader</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when accessing a relative
+URL resource that belongs to a foreign
+context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Character encoding of the content at the input
+resource.
+        </description>
+        <name>charEncoding</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	The basic iteration tag, accepting many different
+        collection types and supporting subsetting and other
+        functionality
+    </description>
+    <name>forEach</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ForEachTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Collection of items to iterate over.
+        </description>
+	<name>items</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.Object</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration begins at the item located at the
+specified index. First item of the collection has
+index 0.
+If items not specified:
+Iteration begins with index set at the value
+specified.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration ends at the item located at the
+specified index (inclusive).
+If items not specified:
+Iteration ends when index reaches the value
+specified.
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step items of
+the collection, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility. Its type depends
+on the object of the underlying collection.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of type
+javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Iterates over tokens, separated by the supplied delimeters
+    </description>
+    <name>forTokens</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String of tokens to iterate over.
+        </description>
+	<name>items</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+The set of delimiters (the characters that
+separate the tokens in the string).
+        </description>
+	<name>delims</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration begins at the token located at the
+specified index. First token has index 0.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration ends at the token located at the
+specified index (inclusive).
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step tokens
+of the string, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of
+type
+javax.servlet.jsp.jstl.core.LoopTag
+Status. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Like &lt;%= ... &gt;, but for expressions.
+    </description> 
+    <name>out</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Default value if the resulting value is null.
+        </description>
+        <name>default</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Determines whether characters &lt;,&gt;,&amp;,'," in the
+resulting string should be converted to their
+corresponding character entity codes. Default value is
+true.
+        </description>
+        <name>escapeXml</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+
+  <tag>
+    <description>
+        Subtag of &lt;choose&gt; that follows &lt;when&gt; tags
+        and runs only if all of the prior conditions evaluated to
+        'false'
+    </description>
+    <name>otherwise</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+        Adds a parameter to a containing 'import' tag's URL.
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the query string parameter.
+        </description>
+        <name>name</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Value of the parameter.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Redirects to a new URL.
+    </description>
+    <name>redirect</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.RedirectTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to redirect to.
+        </description>
+        <name>url</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when redirecting to a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Removes a scoped variable (from a particular scope, if specified).
+    </description>
+    <name>remove</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of the scoped variable to be removed.
+        </description>
+        <name>var</name>
+        <required>true</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+ <tag>
+    <description>
+        Sets the result of an expression evaluation in a 'scope'
+    </description>
+    <name>set</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable to hold the value
+specified in the action. The type of the scoped variable is
+whatever type the value expression evaluates to.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Target object whose property will be set. Must evaluate to
+a JavaBeans object with setter property property, or to a
+java.util.Map object.
+        </description>
+        <name>target</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the property to be set in the target object.
+        </description>
+        <name>property</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Creates a URL with optional query parameters.
+    </description>
+    <name>url</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.UrlTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+processed url. The type of the scoped variable is
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+URL to be processed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when specifying a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Subtag of &lt;choose&gt; that includes its body if its
+	condition evalutes to 'true'
+    </description>
+    <name>when</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or not the
+body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+  </tag>
+
+</taglib>

web-app/WEB-INF/tld/fmt.tld

+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 i18n-capable formatting library</description>
+  <display-name>JSTL fmt</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>fmt</short-name>
+  <uri>http://java.sun.com/jsp/jstl/fmt</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlFmtTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Sets the request character encoding
+    </description>
+    <name>requestEncoding</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.RequestEncodingTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of character encoding to be applied when
+decoding request parameters.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given locale in the locale configuration variable
+    </description>
+    <name>setLocale</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetLocaleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+A String value is interpreted as the
+printable representation of a locale, which
+must contain a two-letter (lower-case)
+language code (as defined by ISO-639),
+and may contain a two-letter (upper-case)
+country code (as defined by ISO-3166).
+Language and country codes must be
+separated by hyphen (-) or underscore
+(_).        
+	</description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Vendor- or browser-specific variant.
+See the java.util.Locale javadocs for
+more information on variants.
+        </description>
+        <name>variant</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of the locale configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Specifies the time zone for any time formatting or parsing actions
+        nested in its body
+    </description>
+    <name>timeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.TimeZoneTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See
+java.util.TimeZone for more information on
+supported time zone formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given time zone in the time zone configuration variable
+    </description>
+    <name>setTimeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetTimeZoneTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See java.util.TimeZone for
+more information on supported time zone
+formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which
+stores the time zone of type
+java.util.TimeZone.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the time zone configuration
+variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle to be used by its tag body
+    </description>
+    <name>bundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.BundleTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Prefix to be prepended to the value of the message
+key of any nested &lt;fmt:message&gt; action.
+        </description>
+        <name>prefix</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle and stores it in the named scoped variable or
+        the bundle configuration variable
+    </description>
+    <name>setBundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which stores
+the i18n localization context of type
+javax.servlet.jsp.jstl.fmt.LocalizationC
+ontext.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the localization context
+configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Maps key to localized message and performs parametric replacement
+    </description>
+    <name>message</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.MessageTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Message key to be looked up.
+        </description>
+        <name>key</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Localization context in whose resource
+bundle the message key is looked up.
+        </description>
+        <name>bundle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the localized message.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Supplies an argument for parametric replacement to a containing
+        &lt;message&gt; tag
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Argument used for parametric replacement.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Formats a numeric value as a number, currency, or percentage
+    </description>
+    <name>formatNumber</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Numeric value to be formatted.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the value is to be
+formatted as number, currency, or
+percentage.
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting pattern.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+ISO 4217 currency code. Applied only
+when formatting currencies (i.e. if type is
+equal to "currency"); ignored otherwise.
+        </description>
+        <name>currencyCode</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Currency symbol. Applied only when
+formatting currencies (i.e. if type is equal
+to "currency"); ignored otherwise.
+        </description>
+        <name>currencySymbol</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the formatted output
+will contain any grouping separators.
+        </description>
+        <name>groupingUsed</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>maxIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>minIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>maxFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>minFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the formatted result as a
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Parses the string representation of a number, currency, or percentage
+    </description>
+    <name>parseNumber</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParseNumberTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String to be parsed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the string in the value
+attribute should be parsed as a number,
+currency, or percentage.
+        </description>
+        <name>type</name></