]> source.dussan.org Git - vaadin-framework.git/commitdiff
Migrate UsingVaadinCDIWithJAASAuthentication
authorErik Lumme <erik@vaadin.com>
Tue, 12 Sep 2017 11:56:53 +0000 (14:56 +0300)
committerErik Lumme <erik@vaadin.com>
Tue, 12 Sep 2017 11:56:53 +0000 (14:56 +0300)
documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc [new file with mode: 0644]
documentation/articles/contents.asciidoc

diff --git a/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc b/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc
new file mode 100644 (file)
index 0000000..d778559
--- /dev/null
@@ -0,0 +1,359 @@
+Servlet 3.0 is awesome, so is CDI. They work well and are a joy to set
+up. Even adding the Vaadin Navigator to the mix isn't an issue, since
+you can use the CDIViewProvider to maintain the injection chains.
+Everything works nicely with annotations, and you don't need to mess
+around with nasty XML. But what if you want to use JAAS? Recently I
+stumbled upon a small dilemma in a customer project where the customer
+wanted to use JAAS in combination with CDI. How in the world was I going
+to combine the two?
+
+For this example I am using JBoss 7.1 for out-of-the-box support for CDI
+and JAAS. I also tried this on Wildfly (the newest JBoss), but ran into
+a bug that Matti has made
+https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/tree/workaround[a
+bit hacky workaround here]. I use a Postgres database, because a
+database-based mechanism is more in the real world then the simplest
+cases. But you can use any authentication mechanism you want, like LDAP,
+AD and so on; you’ll find tutorials for integrating these into the JAAS
+modules of almost any application server.
+
+There is a little bit of configuration involved because of the DB
+connection, but don't be scared. It really is quite easy stuff. OK,
+everyone ready? Lets dive in.
+
+The problem is that you need to define a secure mapping for a servlet in
+web.xml, but maintaining the automatic scanning of Servlet annotations.
+Luckily, we can use both. The servlet mapping (or UI mapping, in our
+case) will take care of combining the two. I am not actually defining a
+Servlet anywhere, but using the Vaadin-CDI addon for this. I simply
+annotate my UIs with the @CDIUI annotation, and they will be picked up
+automatically. The idea is to have an unsecured root UI mapped to /, a
+secured area mapped to /secure, and a login page mapped to /login. For
+the root UI, it looks like this:
+
+....
+@CDIUI
+public class UnsecureUI extends UI {
+
+    @Override
+    protected void init(VaadinRequest request) {
+        final VerticalLayout layout = new VerticalLayout();
+        layout.setMargin(true);
+        setContent(layout);
+
+        layout.addComponent(new Label("unsecure UI"));
+
+        Button b = new Button("Go to secure part");
+        b.addClickListener(new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                String currentURI = getPage().getLocation().toString();
+                getPage().setLocation(currentURI + "secure");
+            }
+        });
+        layout.addComponent(b);
+    }
+}
+....
+
+The CDI addon (more exactly, the CDIUIProvider) will find the UI, and
+will automatically deploy it. You can then start injecting things into
+the UI class, such as a CDIViewProvider for the Navigator:
+
+....
+    @Inject
+    private CDIViewProvider provider;
+
+    @Override
+    protected void init(VaadinRequest request) {
+        Navigator n = new Navigator(this, this);
+        n.addProvider(provider);
+....
+
+Please note that you can configure the Servlet in a multitude of ways;
+you can map the Servlet in your web.xml file as well. Leave out any UI
+definitions, and put this in instead:
+
+....
+<init-param>
+        <param-name>UIProvider</param-name>
+        <param-value>com.vaadin.cdi.CDIUIProvider</param-value>
+</init-param>
+....
+
+This snippet tells Vaadin to use the same classpath scanning as it would
+with Servlet annotations.
+
+So, thats it for the CDI part. What about JAAS? Well, we need to put
+this in web.xml, so lets create the file and add some security
+configuration:
+
+....
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+   xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
+   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
+
+    <security-constraint>
+        <display-name>SecureApplicationConstraint</display-name>
+        <web-resource-collection>
+            <web-resource-name>SecureUI</web-resource-name>
+            <description>Only this UI is protected</description>
+            <url-pattern>/secure/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <description>Only valid users are allowed</description>
+            <role-name>viewer</role-name>
+        </auth-constraint>
+    </security-constraint>
+    <login-config>
+        <auth-method>FORM</auth-method>
+        <realm-name>ApplicationRealm</realm-name>
+        <form-login-config>
+            <form-login-page>/login</form-login-page>
+            <form-error-page>/login</form-error-page>
+        </form-login-config>
+    </login-config>
+    <security-role>
+        <role-name>viewer</role-name>
+    </security-role>
+
+</web-app>
+....
+
+What happens is that the root of our application is not secured; here we
+have an unsecured part of our application. When the user tries to access
+the secure area under /secure, they get redirected to our login UI under
+/login. Any attempt to go to /secure/ without logging in will be blocked
+by the server. We also restrict that any user who tries to access the
+application needs to have the 'viewer' role, or they won't get in.
+
+The secure UI and the login UI are added below:
+
+SecureUI:
+
+....
+@CDIUI("secure")
+public class SecureUI extends UI {
+
+    @Override
+    protected void init(VaadinRequest request) {
+        final VerticalLayout layout = new VerticalLayout();
+        layout.setMargin(true);
+        setContent(layout);
+
+        layout.addComponent(new Label("This is a secure UI! Username is "
+                + request.getUserPrincipal().getName()));
+    }
+}
+....
+
+The secure page isn’t anything special, but you can see how we access
+the user name from the JAAS security context.
+
+LoginUI:
+
+....
+@CDIUI("login")
+public class LoginUI extends UI {
+
+    @Override
+    protected void init(VaadinRequest request) {
+        final VerticalLayout layout = new VerticalLayout();
+        layout.setMargin(true);
+        setContent(layout);
+
+        Button login = new Button("login");
+        login.addClickListener(new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                try {
+                    JaasAccessControl.login("demo", "demo");
+                    Page page = Page.getCurrent();
+                    page.setLocation(page.getLocation());
+                } catch (ServletException e) {
+                    // TODO handle exception
+                    e.printStackTrace();
+                }
+            }
+        });
+        layout.addComponent(login);
+    }
+}
+....
+
+The interesting parts are these:
+
+....
+JaasAccessControl.login("demo", "demo");
+Page page = Page.getCurrent();
+page.setLocation(page.getLocation());
+....
+
+JaasAccessControl is a utility class from the Vaadin-CDI addon; we use
+it to perform programmatic login. I the login succeeds, we refresh the
+page the user is on. Why do we need to do this? Well, let’s consider why
+the login page is visible. The user has tried to access /secure, but
+isn’t logged in. Under the hood, the server realizes this, and serves
+our login page instead without doing a proper redirect. This means the
+users URL doesn’t change; it still says /secure. We refresh the page,
+and since we are logged in, we get the real content of the secure UI.
+
+Now, we could do login with other technologies as well. If you have a
+single-sign-on of some sort, you might want to use the JaasAccessControl
+class to integrate that into your app. You can also do form-based JSP
+login, as you would do in the olden days. The possibilities are truly
+many here. If you do decide on using JSP, here are a couple of helpers
+for you:
+
+Add the following into your login.jsp:
+
+....
+<!-- Vaadin-Refresh -->
+....
+
+Why is this line needed? To answer this I need to tell you what happens
+when an application session times out. When Vaadin requests something
+from the server, the server replies with something else. Typically
+(without JAAS), it is a simple error message saying the session is
+invalid. If we are using JAAS, however, what we get in the response from
+the server is the login page HTML. Vaadin doesn't handle this too well;
+it adds the HTML response to the red notification popup. To fix this, we
+have added a feature to Vaadin that checks the HTML for a specific
+keyword (you guessed it, 'Vaadin-Refresh'), and if it finds it, simply
+reloads the complete page. You can also define a redirect url if you
+want to, but we won't need it here since JAAS will redirect for us. So,
+we add the comment to the JSP so that when a session timeouts, we want
+to be redirected to the login page.
+
+The second thing (still in login.jsp) is this:
+
+....
+<meta http-equiv="refresh" content="${pageContext.session.maxInactiveInterval}">
+....
+
+We add this line so that the login page itself doesn't timeout. Session
+timeouts are active from the first access to the servlet; in our case
+loading the login page. If the user doesn't fill in anything, and the
+timer runs out, the user will get an ugly error message from the server.
+To fix that we simply reload the page, extending the session (or
+creating a new one).
+
+OK, with us so far? We still need a couple of things for JBoss to
+understand what we want to do:
+
+I have a jboss-web.xml inside WEB-INF that tells JBoss which settings to
+use:
+
+....
+    <jboss-web>
+        <security-domain>DBAuth</security-domain>
+    </jboss-web>
+....
+
+Then in the JBoss standalone.xml configuration file, I add the security
+domain params:
+
+....
+    <security-domain name="DBAuth">
+        <authentication>
+            <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
+                <module-option name="dsJndiName" value="java:jboss/datasources/myappdb"/>
+                <module-option name="principalsQuery" value="select password from PRINCIPLES where principal_id=?"/>
+                <module-option name="rolesQuery" value="select user_role, 'Roles' from ROLES where principal_id=?"/>
+            </login-module>
+        </authentication>
+    </security-domain>
+....
+
+The domain that we specify tells the server where to find users and
+passwords. In our case, they can be found in the PRINCIPLES table, with
+roles added to the ROLES table. As you can see, you specify the SQL for
+the query, so you have a lot of freedom in how you do this. Note that we
+are not using any encryption or hashing for the passwords; please don't
+use this configuration for real applications. Instead, you should use a
+custom Login Module class that can compare hashes instead of pure
+strings, and store salted hashes in your database. Implement your class
+by extending the DatabaseServerLoginModule class and change the code
+attribute in the login-module tag to point to your class instead.
+
+Then we need the data source (still in standalone.xml):
+
+....
+    <datasources>
+        <datasource jta="true" jndi-name="java:jboss/datasources/myappdb" pool-name="java:jboss/datasources/myappdb_pool" 
+        enabled="true" use-java-context="true" use-ccm="true">
+            <connection-url>jdbc:postgresql://localhost:5432/myappdb</connection-url>
+            <driver-class>org.postgresql.Driver</driver-class>
+            <driver>postgresql-jdbc4</driver>
+            <pool>
+                <min-pool-size>2</min-pool-size>
+                <max-pool-size>20</max-pool-size>
+                <prefill>true</prefill>
+            </pool>
+            <security>
+                <user-name>demo</user-name>
+                <password>demo</password>
+            </security>
+            <validation>
+                <check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
+                <validate-on-match>false</validate-on-match>
+                <background-validation>false</background-validation>
+                <use-fast-fail>false</use-fast-fail>
+            </validation>
+        </datasource>
+        <drivers>
+            <driver name="postgresql-jdbc4" module="org.postgresql"/>
+        </drivers>
+    </datasources>
+....
+
+As you can see, I'm using a Postgres database. You will need the
+postgres JDBC driver installed under the Wildfly modules directory for
+this to work. And, of course an actual Postgres server with the
+specified database created. In our application we use Hibernate with
+container managed transactions to handle persistence; as this isn't a
+JPA tutorial, so I'll leave that for another day.
+
+But, for completeness sake, here is a short SQL script for the DB.
+Create a database named ‘myappdb’, and run this:
+
+....
+CREATE USER demo WITH PASSWORD 'demo';
+
+CREATE TABLE PRINCIPLES ( principal_id VARCHAR(64) primary key,password VARCHAR(64));
+CREATE TABLE ROLES ( role_item_id integer, principal_id VARCHAR(64),user_role VARCHAR(64));
+
+Grant all privileges on table roles to demo;
+Grant all privileges on table principles to demo;
+
+--Initial data
+Insert into principles values ('demo', 'demo');
+insert into roles values (1, 'demo', 'viewer');
+....
+
+The only thing left is to get the username and roles from inside your
+Vaadin app:
+
+....
+    @Override
+    protected void init(VaadinRequest request) {
+        String username = request.getUserPrincipal().toString();
+        if (request.isUserInRole("viewer")) {
+            // Add admin view to menu
+        }
+....
+
+If you are using the CDI-based navigator, you can also use the
+@RolesAllowed annotation on your views to automatically constrain
+visibility of your views.
+
+That's it, your app will now use database authentication with JAAS and
+CDI. The provided configuration isn't complete, and there are small
+pieces I didn't really cover, but it will work for basic cases. Feel
+free to add comments below.
+
+You might also check out
+https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/[a related
+full app example], that uses built in "FileRealm" in JBoss.
index 386be81b8e3f0fe6fdf052f07bcb96abe6897fbf..f3f961148521c3585c86ae384c0794f938d144a5 100644 (file)
@@ -56,4 +56,5 @@ are great, too.
 - link:VaadinCDI.asciidoc[Vaadin CDI]
 - link:IIInjectionAndScopes.asciidoc[II - Injection and scopes]
 - link:CreatingSecureVaadinApplicationsUsingJEE6.asciidoc[Creating secure Vaadin applications using JEE6]
+- link:UsingVaadinCDIWithJAASAuthentication.asciidoc[Using Vaadin CDI with JAAS authentication]
 - link:CreatingAUIExtension.asciidoc[Creating a UI extension]