From 4ce471a57cb33461cc255aeed9b2d130f9009510 Mon Sep 17 00:00:00 2001 From: Erik Lumme Date: Tue, 12 Sep 2017 14:56:53 +0300 Subject: [PATCH] Migrate UsingVaadinCDIWithJAASAuthentication --- ...ngVaadinCDIWithJAASAuthentication.asciidoc | 359 ++++++++++++++++++ documentation/articles/contents.asciidoc | 1 + 2 files changed, 360 insertions(+) create mode 100644 documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc diff --git a/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc b/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc new file mode 100644 index 0000000000..d778559543 --- /dev/null +++ b/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc @@ -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: + +.... + + UIProvider + com.vaadin.cdi.CDIUIProvider + +.... + +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: + +.... + + + + SecureApplicationConstraint + + SecureUI + Only this UI is protected + /secure/* + + + Only valid users are allowed + viewer + + + + FORM + ApplicationRealm + + /login + /login + + + + viewer + + + +.... + +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: + +.... + +.... + +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: + +.... + +.... + +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: + +.... + + DBAuth + +.... + +Then in the JBoss standalone.xml configuration file, I add the security +domain params: + +.... + + + + + + + + + +.... + +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): + +.... + + + jdbc:postgresql://localhost:5432/myappdb + org.postgresql.Driver + postgresql-jdbc4 + + 2 + 20 + true + + + demo + demo + + + SELECT 1 + false + false + false + + + + + + +.... + +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. diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index 386be81b8e..f3f9611485 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -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] -- 2.39.5