You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UsingVaadinCDIWithJAASAuthentication.asciidoc 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. ---
  2. title: Using Vaadin CDI With JAAS Authentication
  3. order: 50
  4. layout: page
  5. ---
  6. [[using-vaadin-cdi-with-jaas-authentication]]
  7. = Using Vaadin CDI with JAAS authentication
  8. Servlet 3.0 is awesome, so is CDI. They work well and are a joy to set
  9. up. Even adding the Vaadin Navigator to the mix isn't an issue, since
  10. you can use the CDIViewProvider to maintain the injection chains.
  11. Everything works nicely with annotations, and you don't need to mess
  12. around with nasty XML. But what if you want to use JAAS? Recently I
  13. stumbled upon a small dilemma in a customer project where the customer
  14. wanted to use JAAS in combination with CDI. How in the world was I going
  15. to combine the two?
  16. For this example I am using JBoss 7.1 for out-of-the-box support for CDI
  17. and JAAS. I also tried this on Wildfly (the newest JBoss), but ran into
  18. a bug that Matti has made
  19. https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/tree/workaround[a
  20. bit hacky workaround here]. I use a Postgres database, because a
  21. database-based mechanism is more in the real world then the simplest
  22. cases. But you can use any authentication mechanism you want, like LDAP,
  23. AD and so on; you’ll find tutorials for integrating these into the JAAS
  24. modules of almost any application server.
  25. There is a little bit of configuration involved because of the DB
  26. connection, but don't be scared. It really is quite easy stuff. OK,
  27. everyone ready? Lets dive in.
  28. The problem is that you need to define a secure mapping for a servlet in
  29. web.xml, but maintaining the automatic scanning of Servlet annotations.
  30. Luckily, we can use both. The servlet mapping (or UI mapping, in our
  31. case) will take care of combining the two. I am not actually defining a
  32. Servlet anywhere, but using the Vaadin-CDI addon for this. I simply
  33. annotate my UIs with the @CDIUI annotation, and they will be picked up
  34. automatically. The idea is to have an unsecured root UI mapped to /, a
  35. secured area mapped to /secure, and a login page mapped to /login. For
  36. the root UI, it looks like this:
  37. [source,java]
  38. ....
  39. @CDIUI
  40. public class UnsecureUI extends UI {
  41. @Override
  42. protected void init(VaadinRequest request) {
  43. final VerticalLayout layout = new VerticalLayout();
  44. layout.setMargin(true);
  45. setContent(layout);
  46. layout.addComponent(new Label("unsecure UI"));
  47. Button b = new Button("Go to secure part");
  48. b.addClickListener(new ClickListener() {
  49. @Override
  50. public void buttonClick(ClickEvent event) {
  51. String currentURI = getPage().getLocation().toString();
  52. getPage().setLocation(currentURI + "secure");
  53. }
  54. });
  55. layout.addComponent(b);
  56. }
  57. }
  58. ....
  59. The CDI addon (more exactly, the CDIUIProvider) will find the UI, and
  60. will automatically deploy it. You can then start injecting things into
  61. the UI class, such as a CDIViewProvider for the Navigator:
  62. [source,java]
  63. ....
  64. @Inject
  65. private CDIViewProvider provider;
  66. @Override
  67. protected void init(VaadinRequest request) {
  68. Navigator n = new Navigator(this, this);
  69. n.addProvider(provider);
  70. ....
  71. Please note that you can configure the Servlet in a multitude of ways;
  72. you can map the Servlet in your web.xml file as well. Leave out any UI
  73. definitions, and put this in instead:
  74. [source,xml]
  75. ....
  76. <init-param>
  77. <param-name>UIProvider</param-name>
  78. <param-value>com.vaadin.cdi.CDIUIProvider</param-value>
  79. </init-param>
  80. ....
  81. This snippet tells Vaadin to use the same classpath scanning as it would
  82. with Servlet annotations.
  83. So, thats it for the CDI part. What about JAAS? Well, we need to put
  84. this in web.xml, so lets create the file and add some security
  85. configuration:
  86. [source,xml]
  87. ....
  88. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  89. xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  90. http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  91. <security-constraint>
  92. <display-name>SecureApplicationConstraint</display-name>
  93. <web-resource-collection>
  94. <web-resource-name>SecureUI</web-resource-name>
  95. <description>Only this UI is protected</description>
  96. <url-pattern>/secure/*</url-pattern>
  97. </web-resource-collection>
  98. <auth-constraint>
  99. <description>Only valid users are allowed</description>
  100. <role-name>viewer</role-name>
  101. </auth-constraint>
  102. </security-constraint>
  103. <login-config>
  104. <auth-method>FORM</auth-method>
  105. <realm-name>ApplicationRealm</realm-name>
  106. <form-login-config>
  107. <form-login-page>/login</form-login-page>
  108. <form-error-page>/login</form-error-page>
  109. </form-login-config>
  110. </login-config>
  111. <security-role>
  112. <role-name>viewer</role-name>
  113. </security-role>
  114. </web-app>
  115. ....
  116. What happens is that the root of our application is not secured; here we
  117. have an unsecured part of our application. When the user tries to access
  118. the secure area under /secure, they get redirected to our login UI under
  119. /login. Any attempt to go to /secure/ without logging in will be blocked
  120. by the server. We also restrict that any user who tries to access the
  121. application needs to have the 'viewer' role, or they won't get in.
  122. The secure UI and the login UI are added below:
  123. SecureUI:
  124. [source,java]
  125. ....
  126. @CDIUI("secure")
  127. public class SecureUI extends UI {
  128. @Override
  129. protected void init(VaadinRequest request) {
  130. final VerticalLayout layout = new VerticalLayout();
  131. layout.setMargin(true);
  132. setContent(layout);
  133. layout.addComponent(new Label("This is a secure UI! Username is "
  134. + request.getUserPrincipal().getName()));
  135. }
  136. }
  137. ....
  138. The secure page isn’t anything special, but you can see how we access
  139. the user name from the JAAS security context.
  140. LoginUI:
  141. [source,java]
  142. ....
  143. @CDIUI("login")
  144. public class LoginUI extends UI {
  145. @Override
  146. protected void init(VaadinRequest request) {
  147. final VerticalLayout layout = new VerticalLayout();
  148. layout.setMargin(true);
  149. setContent(layout);
  150. Button login = new Button("login");
  151. login.addClickListener(new ClickListener() {
  152. @Override
  153. public void buttonClick(ClickEvent event) {
  154. try {
  155. JaasAccessControl.login("demo", "demo");
  156. Page page = Page.getCurrent();
  157. page.setLocation(page.getLocation());
  158. } catch (ServletException e) {
  159. // TODO handle exception
  160. e.printStackTrace();
  161. }
  162. }
  163. });
  164. layout.addComponent(login);
  165. }
  166. }
  167. ....
  168. The interesting parts are these:
  169. [source,java]
  170. ....
  171. JaasAccessControl.login("demo", "demo");
  172. Page page = Page.getCurrent();
  173. page.setLocation(page.getLocation());
  174. ....
  175. JaasAccessControl is a utility class from the Vaadin-CDI addon; we use
  176. it to perform programmatic login. If the login succeeds, we refresh the
  177. page the user is on. Why do we need to do this? Well, let’s consider why
  178. the login page is visible. The user has tried to access /secure, but
  179. isn’t logged in. Under the hood, the server realizes this, and serves
  180. our login page instead without doing a proper redirect. This means the
  181. users URL doesn’t change; it still says /secure. We refresh the page,
  182. and since we are logged in, we get the real content of the secure UI.
  183. Now, we could do login with other technologies as well. If you have a
  184. single-sign-on of some sort, you might want to use the JaasAccessControl
  185. class to integrate that into your app. You can also do form-based JSP
  186. login, as you would do in the olden days. The possibilities are truly
  187. many here. If you do decide on using JSP, here are a couple of helpers
  188. for you:
  189. Add the following into your login.jsp:
  190. [source,html]
  191. ....
  192. <!-- Vaadin-Refresh -->
  193. ....
  194. Why is this line needed? To answer this I need to tell you what happens
  195. when an application session times out. When Vaadin requests something
  196. from the server, the server replies with something else. Typically
  197. (without JAAS), it is a simple error message saying the session is
  198. invalid. If we are using JAAS, however, what we get in the response from
  199. the server is the login page HTML. Vaadin doesn't handle this too well;
  200. it adds the HTML response to the red notification popup. To fix this, we
  201. have added a feature to Vaadin that checks the HTML for a specific
  202. keyword (you guessed it, 'Vaadin-Refresh'), and if it finds it, simply
  203. reloads the complete page. You can also define a redirect url if you
  204. want to, but we won't need it here since JAAS will redirect for us. So,
  205. we add the comment to the JSP so that when a session timeouts, we want
  206. to be redirected to the login page.
  207. The second thing (still in login.jsp) is this:
  208. [source,html]
  209. ....
  210. <meta http-equiv="refresh" content="${pageContext.session.maxInactiveInterval}">
  211. ....
  212. We add this line so that the login page itself doesn't timeout. Session
  213. timeouts are active from the first access to the servlet; in our case
  214. loading the login page. If the user doesn't fill in anything, and the
  215. timer runs out, the user will get an ugly error message from the server.
  216. To fix that we simply reload the page, extending the session (or
  217. creating a new one).
  218. OK, with us so far? We still need a couple of things for JBoss to
  219. understand what we want to do:
  220. I have a jboss-web.xml inside WEB-INF that tells JBoss which settings to
  221. use:
  222. [source,xml]
  223. ....
  224. <jboss-web>
  225. <security-domain>DBAuth</security-domain>
  226. </jboss-web>
  227. ....
  228. Then in the JBoss standalone.xml configuration file, I add the security
  229. domain params:
  230. [source,xml]
  231. ....
  232. <security-domain name="DBAuth">
  233. <authentication>
  234. <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
  235. <module-option name="dsJndiName" value="java:jboss/datasources/myappdb"/>
  236. <module-option name="principalsQuery" value="select password from PRINCIPLES where principal_id=?"/>
  237. <module-option name="rolesQuery" value="select user_role, 'Roles' from ROLES where principal_id=?"/>
  238. </login-module>
  239. </authentication>
  240. </security-domain>
  241. ....
  242. The domain that we specify tells the server where to find users and
  243. passwords. In our case, they can be found in the PRINCIPLES table, with
  244. roles added to the ROLES table. As you can see, you specify the SQL for
  245. the query, so you have a lot of freedom in how you do this. Note that we
  246. are not using any encryption or hashing for the passwords; please don't
  247. use this configuration for real applications. Instead, you should use a
  248. custom Login Module class that can compare hashes instead of pure
  249. strings, and store salted hashes in your database. Implement your class
  250. by extending the DatabaseServerLoginModule class and change the code
  251. attribute in the login-module tag to point to your class instead.
  252. Then we need the data source (still in standalone.xml):
  253. [source,xml]
  254. ....
  255. <datasources>
  256. <datasource jta="true" jndi-name="java:jboss/datasources/myappdb" pool-name="java:jboss/datasources/myappdb_pool"
  257. enabled="true" use-java-context="true" use-ccm="true">
  258. <connection-url>jdbc:postgresql://localhost:5432/myappdb</connection-url>
  259. <driver-class>org.postgresql.Driver</driver-class>
  260. <driver>postgresql-jdbc4</driver>
  261. <pool>
  262. <min-pool-size>2</min-pool-size>
  263. <max-pool-size>20</max-pool-size>
  264. <prefill>true</prefill>
  265. </pool>
  266. <security>
  267. <user-name>demo</user-name>
  268. <password>demo</password>
  269. </security>
  270. <validation>
  271. <check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
  272. <validate-on-match>false</validate-on-match>
  273. <background-validation>false</background-validation>
  274. <use-fast-fail>false</use-fast-fail>
  275. </validation>
  276. </datasource>
  277. <drivers>
  278. <driver name="postgresql-jdbc4" module="org.postgresql"/>
  279. </drivers>
  280. </datasources>
  281. ....
  282. As you can see, I'm using a Postgres database. You will need the
  283. postgres JDBC driver installed under the Wildfly modules directory for
  284. this to work. And, of course an actual Postgres server with the
  285. specified database created. In our application we use Hibernate with
  286. container managed transactions to handle persistence; as this isn't a
  287. JPA tutorial, so I'll leave that for another day.
  288. But, for completeness sake, here is a short SQL script for the DB.
  289. Create a database named ‘myappdb’, and run this:
  290. [source,sql]
  291. ....
  292. CREATE USER demo WITH PASSWORD 'demo';
  293. CREATE TABLE PRINCIPLES ( principal_id VARCHAR(64) primary key,password VARCHAR(64));
  294. CREATE TABLE ROLES ( role_item_id integer, principal_id VARCHAR(64),user_role VARCHAR(64));
  295. Grant all privileges on table roles to demo;
  296. Grant all privileges on table principles to demo;
  297. --Initial data
  298. Insert into principles values ('demo', 'demo');
  299. insert into roles values (1, 'demo', 'viewer');
  300. ....
  301. The only thing left is to get the username and roles from inside your
  302. Vaadin app:
  303. [source,java]
  304. ....
  305. @Override
  306. protected void init(VaadinRequest request) {
  307. String username = request.getUserPrincipal().toString();
  308. if (request.isUserInRole("viewer")) {
  309. // Add admin view to menu
  310. }
  311. ....
  312. If you are using the CDI-based navigator, you can also use the
  313. @RolesAllowed annotation on your views to automatically constrain
  314. visibility of your views.
  315. That's it, your app will now use database authentication with JAAS and
  316. CDI. The provided configuration isn't complete, and there are small
  317. pieces I didn't really cover, but it will work for basic cases. Feel
  318. free to add comments below.
  319. You might also check out
  320. https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/[a related
  321. full app example], that uses built in "FileRealm" in JBoss.