JPA-Ticket-Registry.md 11.7 KB
Newer Older
Misagh Moayyed's avatar
Misagh Moayyed committed
1
2
3
4
5
6
7
---
layout: default
title: CAS - JPA Ticket Registry
---


# JPA Ticket Registry
Misagh Moayyed's avatar
Misagh Moayyed committed
8
9
The JPA Ticket Registry allows CAS to store client authenticated state 
data (tickets) in a database back-end such as MySQL.
Misagh Moayyed's avatar
Misagh Moayyed committed
10

Misagh Moayyed's avatar
Misagh Moayyed committed
11
12
13
14
15
16
17
18
<div class="alert alert-warning"><strong>Usage Warning!</strong><p>Using a RDBMS as 
the back-end persistence choice for Ticket Registry state management is a fairly unnecessary and complicated 
process. Ticket registries generally do not need the durability that comes with RDBMS and unless 
you are already outfitted with clustered RDBMS technology and the resources to manage it, 
the complexity is likely not worth the trouble. Given the proliferation of hardware virtualization 
and the redundancy and vertical scaling they often provide, more suitable recommendation would be 
the default in-memory ticket registry for a single node CAS deployment and distributed cache-based 
registries for higher availability.</p></div>
Misagh Moayyed's avatar
Misagh Moayyed committed
19
20
21
22
23
24
25


# Configuration

- Adjust the `src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml` with the following:

{% highlight xml %}
Misagh Moayyed's avatar
Misagh Moayyed committed
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<bean
    id="dataSource"
    class="com.mchange.v2.c3p0.ComboPooledDataSource"
    p:driverClass="${database.driverClass:org.hsqldb.jdbcDriver}"
    p:jdbcUrl="${database.url:jdbc:hsqldb:mem:cas-ticket-registry}"
    p:user="${database.user:sa}"
    p:password="${database.password:}"
    p:initialPoolSize="${database.pool.minSize:6}"
    p:minPoolSize="${database.pool.minSize:6}"
    p:maxPoolSize="${database.pool.maxSize:18}"
    p:maxIdleTimeExcessConnections="${database.pool.maxIdleTime:1000}"
    p:checkoutTimeout="${database.pool.maxWait:2000}"
    p:acquireIncrement="${database.pool.acquireIncrement:16}"
    p:acquireRetryAttempts="${database.pool.acquireRetryAttempts:5}"
    p:acquireRetryDelay="${database.pool.acquireRetryDelay:2000}"
    p:idleConnectionTestPeriod="${database.pool.idleConnectionTestPeriod:30}"
    p:preferredTestQuery="${database.pool.connectionHealthQuery:select 1}"
/>

Misagh Moayyed's avatar
Misagh Moayyed committed
45
46
47
48
<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.JpaTicketRegistry" />

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

Misagh Moayyed's avatar
Misagh Moayyed committed
49
50
51
52
53
54
55
<util:list id="packagesToScan">
    <value>org.jasig.cas.services</value>
    <value>org.jasig.cas.ticket</value>
    <value>org.jasig.cas.adaptors.jdbc</value>
</util:list>

<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
Misagh Moayyed's avatar
Misagh Moayyed committed
56
57
58
      id="jpaVendorAdapter"
      p:generateDdl="true"
      p:showSql="true" />
Misagh Moayyed's avatar
Misagh Moayyed committed
59
60

<bean id="entityManagerFactory"
Misagh Moayyed's avatar
Misagh Moayyed committed
61
62
63
64
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
      p:dataSource-ref="dataSource"
      p:jpaVendorAdapter-ref="jpaVendorAdapter"
      p:packagesToScan-ref="packagesToScan">
Misagh Moayyed's avatar
Misagh Moayyed committed
65
    <property name="jpaProperties">
Misagh Moayyed's avatar
Misagh Moayyed committed
66
67
68
69
70
        <props>
            <prop key="hibernate.dialect">${database.dialect:org.hibernate.dialect.HSQLDialect}</prop>
            <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
            <prop key="hibernate.jdbc.batch_size">${database.batchSize:1}</prop>
        </props>
Misagh Moayyed's avatar
Misagh Moayyed committed
71
    </property>
Misagh Moayyed's avatar
Misagh Moayyed committed
72
73
74
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
Misagh Moayyed's avatar
Misagh Moayyed committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
      p:entityManagerFactory-ref="entityManagerFactory" />

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>

        <tx:method name="delete*" read-only="false"/>
        <tx:method name="save*" read-only="false"/>
        <tx:method name="update*" read-only="false"/>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="servicesManagerOperations" expression="execution(* org.jasig.cas.services.JpaServiceRegistryDaoImpl.*(..))"/>
    <aop:pointcut id="ticketRegistryOperations" expression="execution(* org.jasig.cas.ticket.registry.JpaTicketRegistry.*(..))"/>
    <aop:pointcut id="ticketRegistryLockingOperations" expression="execution(* org.jasig.cas.ticket.registry.support.JpaLockingStrategy.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="servicesManagerOperations"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="ticketRegistryOperations"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="ticketRegistryLockingOperations"/>
</aop:config>
Misagh Moayyed's avatar
Misagh Moayyed committed
95
96


Misagh Moayyed's avatar
Misagh Moayyed committed
97
<bean id="ticketRegistryCleaner"
Misagh Moayyed's avatar
Misagh Moayyed committed
98
      class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"
Misagh Moayyed's avatar
Misagh Moayyed committed
99
      c:centralAuthenticationService-ref="centralAuthenticationService"
Misagh Moayyed's avatar
Misagh Moayyed committed
100
      c:ticketRegistry-ref="ticketRegistry"
Misagh Moayyed's avatar
Misagh Moayyed committed
101
      p:lock-ref="cleanerLock"/>
Misagh Moayyed's avatar
Misagh Moayyed committed
102
103

<bean id="cleanerLock" class="org.jasig.cas.ticket.registry.support.JpaLockingStrategy"
Misagh Moayyed's avatar
Misagh Moayyed committed
104
105
      p:uniqueId="${host.name}"
      p:applicationId="cas-ticket-registry-cleaner" />
Misagh Moayyed's avatar
Misagh Moayyed committed
106
107

<bean id="jobDetailTicketRegistryCleaner"
Misagh Moayyed's avatar
Misagh Moayyed committed
108
109
110
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
      p:targetObject-ref="ticketRegistryCleaner"
      p:targetMethod="clean" />
Misagh Moayyed's avatar
Misagh Moayyed committed
111
112

<bean id="triggerJobDetailTicketRegistryCleaner"
Misagh Moayyed's avatar
Misagh Moayyed committed
113
114
115
116
      class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"
      p:jobDetail-ref="jobDetailTicketRegistryCleaner"
      p:startDelay="20000"
      p:repeatInterval="5000000" />
Misagh Moayyed's avatar
Misagh Moayyed committed
117
118
119
{% endhighlight %}


Misagh Moayyed's avatar
Misagh Moayyed committed
120
##Cleaner Locking Strategy
Misagh Moayyed's avatar
Misagh Moayyed committed
121
122
The above shows a JPA 2.0 implementation of an exclusive, non-reentrant lock, 
`JpaLockingStrategy`, to be used with the JPA-backed ticket registry.
Misagh Moayyed's avatar
Misagh Moayyed committed
123
124
125

This will configure the cleaner with the following defaults:

Misagh Moayyed's avatar
Misagh Moayyed committed
126
127
128
129
130
131
132
133
| Field                             | Default
|-----------------------------------+---------------------------------------+
| `tableName`                       | `LOCKS`
| `uniqueIdColumnName`              | `UNIQUE_ID`
| `applicationIdColumnName`         | `APPLICATION_ID`
| `expirationDataColumnName`        | `EXPIRATION_DATE`
| `platform`                        | SQL92
| `lockTimeout`                     | 3600 (1 hour)
Misagh Moayyed's avatar
Misagh Moayyed committed
134
135
136
137
138


# Database Configuration

## JDBC Driver
Misagh Moayyed's avatar
Misagh Moayyed committed
139
140
141
CAS must have access to the appropriate JDBC driver for the database. Once you have obtained 
the appropriate driver and configured the data source, place the JAR inside the lib directory 
of your web server environment (i.e. `$TOMCAT_HOME/lib`)
Misagh Moayyed's avatar
Misagh Moayyed committed
142
143
144


## Schema
Misagh Moayyed's avatar
Misagh Moayyed committed
145
146
147
If the user has sufficient privileges on start up, the database tables should be created. 
The database user MUST have `CREATE/ALTER` privileges to take advantage of automatic 
schema generation and schema updates.
Misagh Moayyed's avatar
Misagh Moayyed committed
148
149
150


## Deadlocks
Misagh Moayyed's avatar
Misagh Moayyed committed
151
152
153
154
155
The Hibernate SchemaExport DDL creation tool *may* fail to create two very import indices 
when generating the ticket tables. The absence of these indices dramatically increases the 
potential for database deadlocks under load.
If the indices were not created you should manually create them before placing your CAS 
configuration into a production environment.
Misagh Moayyed's avatar
Misagh Moayyed committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

To review indices, you may use the following MYSQL-based sample code below:

{% highlight sql %}
show index from SERVICETICKET where column_name='ticketGrantingTicket_ID';
show index from TICKETGRANTINGTICKET where column_name='ticketGrantingTicket_ID';
{% endhighlight %}

To create indices that are missing, you may use the following sample code below:


### MYSQL
{% highlight sql %}
CREATE INDEX ST_TGT_FK_I ON SERVICETICKET (ticketGrantingTicket_ID);
CREATE INDEX ST_TGT_FK_I ON TICKETGRANTINGTICKET (ticketGrantingTicket_ID);
{% endhighlight %}


###ORACLE
{% highlight sql %}
CREATE INDEX "ST_TGT_FK_I"
  ON SERVICETICKET ("TICKETGRANTINGTICKET_ID")
  COMPUTE STATISTICS;
Misagh Moayyed's avatar
Misagh Moayyed committed
179

Misagh Moayyed's avatar
Misagh Moayyed committed
180
181
182
183
184
185
186
187
188
/** Create index on TGT self-referential foreign-key constraint */
CREATE INDEX "TGT_TGT_FK_I"
  ON TICKETGRANTINGTICKET ("TICKETGRANTINGTICKET_ID")
  COMPUTE STATISTICS;
{% endhighlight %}


## Ticket Cleanup

Misagh Moayyed's avatar
Misagh Moayyed committed
189
190
191
192
The use `JpaLockingStrategy` is strongly recommended for HA environments where 
multiple nodes are attempting ticket cleanup on a shared database. 
`JpaLockingStrategy` can auto-generate the schema for the target platform.  
A representative schema is provided below that applies to PostgreSQL:
Misagh Moayyed's avatar
Misagh Moayyed committed
193
194
195
196
197
198
199
200
201
202
203
204
205


{% highlight sql %}
CREATE TABLE locks (
 application_id VARCHAR(50) NOT NULL,
 unique_id VARCHAR(50) NULL,
 expiration_date TIMESTAMP NULL
);

ALTER TABLE locks ADD CONSTRAINT pk_locks
 PRIMARY KEY (application_id);
{% endhighlight %}

Misagh Moayyed's avatar
Misagh Moayyed committed
206
207
208
209
<div class="alert alert-warning"><strong>Platform-Specific Issues</strong><p>The exact DDL to create 
the LOCKS table may differ from the above. For example, on Oracle platforms 
the `expiration_date` column must be of type `DAT`E.  Use the `JpaLockingStrategy` 
which can create and update the schema automatically to avoid platform-specific schema issues.</p></div>
Misagh Moayyed's avatar
Misagh Moayyed committed
210
211
212
213


## Connection Pooling

Misagh Moayyed's avatar
Misagh Moayyed committed
214
215
216
It is ***strongly*** recommended that database connection pooling be used in a p
production environment. Based on the example above, the following pool configuration parameters are provided 
for information only and may serve as a reasonable starting point for configuring a production database connection pool.
Misagh Moayyed's avatar
Misagh Moayyed committed
217
218
219

<div class="alert alert-info"><strong>Usage Tip</strong><p>Note the health check query is specific to PostgreSQL.</p></div>

Misagh Moayyed's avatar
Misagh Moayyed committed
220
{% highlight properties %}
Misagh Moayyed's avatar
Misagh Moayyed committed
221
222
223
224
225
226
227
228
# == Basic database connection pool configuration ==
database.dialect=org.hibernate.dialect.PostgreSQLDialect
database.driverClass=org.postgresql.Driver
database.url=jdbc:postgresql://somehost.vt.edu/cas?ssl=true
database.user=somebody
database.password=meaningless
database.pool.minSize=6
database.pool.maxSize=18
Misagh Moayyed's avatar
Misagh Moayyed committed
229

Misagh Moayyed's avatar
Misagh Moayyed committed
230
231
232
# Maximum amount of time to wait in ms for a connection to become
# available when the pool is exhausted
database.pool.maxWait=10000
Misagh Moayyed's avatar
Misagh Moayyed committed
233

Misagh Moayyed's avatar
Misagh Moayyed committed
234
235
236
# Amount of time in seconds after which idle connections
# in excess of minimum size are pruned.
database.pool.maxIdleTime=120
Misagh Moayyed's avatar
Misagh Moayyed committed
237

Misagh Moayyed's avatar
Misagh Moayyed committed
238
239
240
241
# Number of connections to obtain on pool exhaustion condition.
# The maximum pool size is always respected when acquiring
# new connections.
database.pool.acquireIncrement=6
Misagh Moayyed's avatar
Misagh Moayyed committed
242

Misagh Moayyed's avatar
Misagh Moayyed committed
243
# == Connection testing settings ==
Misagh Moayyed's avatar
Misagh Moayyed committed
244

Misagh Moayyed's avatar
Misagh Moayyed committed
245
246
247
# Period in s at which a health query will be issued on idle
# connections to determine connection liveliness.
database.pool.idleConnectionTestPeriod=30
Misagh Moayyed's avatar
Misagh Moayyed committed
248

Misagh Moayyed's avatar
Misagh Moayyed committed
249
250
# Query executed periodically to test health
database.pool.connectionHealthQuery=select 1
Misagh Moayyed's avatar
Misagh Moayyed committed
251

Misagh Moayyed's avatar
Misagh Moayyed committed
252
# == Database recovery settings ==
Misagh Moayyed's avatar
Misagh Moayyed committed
253

Misagh Moayyed's avatar
Misagh Moayyed committed
254
255
256
# Number of times to retry acquiring a _new_ connection
# when an error is encountered during acquisition.
database.pool.acquireRetryAttempts=5
Misagh Moayyed's avatar
Misagh Moayyed committed
257

Misagh Moayyed's avatar
Misagh Moayyed committed
258
# Amount of time in ms to wait between successive acquire retry attempts.
Misagh Moayyed's avatar
Misagh Moayyed committed
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
database.pool.acquireRetryDelay=2000
{% endhighlight %}


# Platform Considerations

## MySQL

### Use InnoDB Tables
The use of InnoDB tables is strongly recommended for the MySQL platform for a couple reasons:

- InnoDB provides referential integrity that is helpful for preventing orphaned records in ticket tables.
- Provides better locking semantics (e.g. support for SELECT ... FOR UPDATE) than the default MyISAM table type.

InnoDB tables are easily specified via the use of the following Hibernate dialect:

{% highlight xml %}
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
Misagh Moayyed's avatar
Misagh Moayyed committed
277

Misagh Moayyed's avatar
Misagh Moayyed committed
278
279
280
281
282
283
<!-- OR for MySQL 5.x use the following instead -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
{% endhighlight %}


###BLOB vs LONGBLOB
Misagh Moayyed's avatar
Misagh Moayyed committed
284
285
286
287
288
Hibernate on recent versions of MySQL (e.g. 5.1) properly maps the `@Lob` JPA annotation onto type `LONGBLOB`, 
which is very important since these fields commonly store serialized graphs of Java objects that 
grow proportionally with CAS SSO session lifetime. Under some circumstances, Hibernate may treat 
these columns as type `BLOB`, which have storage limits that are easily exceeded. It is 
recommended that the generated schema be reviewed and any BLOB type columns be converted to `LONGBLOB`.
Misagh Moayyed's avatar
Misagh Moayyed committed
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

The following MySQL statement would change this `SERVICES_GRANTED_ACCESS_TO` column's type to `LONGBLOB`:

{% highlight sql %}
ALTER TABLE TICKETGRANTINGTICKET MODIFY SERVICES_GRANTED_ACCESS_TO LONGBLOB;
{% endhighlight %}


###Case Sensitive Schema
It may necessary to force lowercase schema names in the MySQL configuration:

Adjust the `my.cnf` file to include the following:
{% highlight bash %}
lower-case-table-names = 1
{% endhighlight %}