Een introductie tot Spring security

April 6th, 2013 | 11 min read | Spring, Spring MVC, Spring security, Web

Ik was al een tijdje van plan een tutorial rond Spring security te maken en nu is deze er dan ook eindelijk. Ik heb ondertussen al enkele tutorials gemaakt rond Spring web applicaties maar we hebben het daar nog nooit gehad over authenticatie en authorisatie.
In deze tutorial ga ik een eenvoudige webapplicatie maken met een in- en een uitlogscherm (authenticatie) en die op basis van de rechten van de gebruiker (autorisatie) de juiste pagina’s zal tonen.

Heads up!

In deze tutorial wordt met regelmaat verwezen naar mijn vorige Java tutorials. Het is dus handig dat je deze (zeker de eerste) eens doorneemt.

Project opzetten

Net zoals altijd gaan we een Spring web applicatie maken, ik raad je daarom aan om zeker mijn Spring webapp tutorial eerst door te lezen. Maak een Maven webapp project aan en zorg ervoor dat je onderstaand resultaat krijgt.

project-setup

Alles is zeer gelijkaardig aan al m’n andere tutorials alleen moet je er nu op letten dat je ook een Spring bean configuratiebestand voorziet met de naam

spring-security.xml

en dat je ook enkele views voorziet (

index.jsp

en

admin.jsp

).

Maven configuratie

Als eerste stap gaan we nu de nodige dependencies toevoegen. De dependencies die je nodig hebt zijn de volgende:

<!-- Spring framework -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- Web -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>${jstl.version}</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

Zoals je kan ziet hebben we hier enkele zeer bekende dependencies tussen zitten (JSTL en Spring dependencies), wat er echter wel nieuw is zijn de dependencies die te maken hebben met Spring security. Zo is er spring-security-core en spring-security-config die de basis vormen, spring-security-web om dit op web applicaties toe te passen en spring-security-taglibs die enkele JSP tags aanbiedt.

Zoals gewoonlijk plaats ik de versienummers in aparte properties (hergebruik en een centraal punt voor dependency versiebeheer), de nodige properties:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <!-- Versions -->
    <spring.version>3.1.2.RELEASE</spring.version>
    <jstl.version>1.2</jstl.version>
</properties>

Web descriptor

De web descriptor is het volgende waar we enkele aanpassingen aan moeten maken. Omdat Spring security een extra “laag” is bovenop de webapplicatie, zullen we in de web descriptor een filter moeten aanmaken waardoor Spring security uitgevoerd kan worden (anders dan wordt de webapplicatie uitgevoerd zonder authenticatie/autorisatie).

De code voor de web descriptor (

web.xml

) is de volgende:

<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    id="WebApp_ID" version="2.4"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Spring security example</display-name>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

ZOals je kan ziet is veel hiervan hetzelfde als in de vorige tutorials, zo hebben we hier ook een springmvc servlet en de Spring listener (die alle interactie doorstuurt naar de controllers).
Wat echter wel nieuw is is de filter die als een soort van proxy te werk gaat (dit is nog een extra stap alvorens de controller bereikt wordt).

Spring configuratie

Een spring project zal uiteraard niet werken zonder enige vorm van configuratie. De configuratie voor dit voorbeeld is verspreid over drie bestanden, applicationContext.xml, springmvc-servlet.xml en spring-security.xml.

Application context

In de application context (

src/main/webapp/WEB-INF/applicationContext.xml

) gaan we de nodige resources importeren, in dit geval is dat spring-security.xml, dit doen we met:

<?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.xsd">

    <import resource="classpath:spring-security.xml" />
</beans>

MVC configuratie

De MVC configuratie (

src/main/resources/springmvc-servlet.xml

) is dezelfde als altijd, hiervoor gebruiken we de volgende code:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <context:component-scan base-package="be.g00glen00b.controller" />
    <context:annotation-config />
</beans>

Spring security

Ten slotte hebben we natuurlijk de configuratie voor Spring security (

src/main/resources/spring-security.xml

), hiervoor gebruiken we:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <security:http auto-config="true">
        <security:logout logout-url="/logout" logout-success-url="/logout-success" />

        <security:intercept-url pattern="/admin*" access="ROLE_ADMIN" />
        <security:intercept-url pattern="/user*" access="ROLE_USER" />
    </security:http>

    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="user" password="user123" authorities="ROLE_USER" />
                <security:user name="admin" password="admin123" authorities="ROLE_ADMIN,ROLE_USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

Indien je dit bestand zelf van scratch wilt maken moet je er rekening mee houden dat je gebruikt maakt van de security namespace, deze kan je met de Spring Eclipse integratie eenvoudig configureren door onderaan de editor op de tab namespaces te klikken en security aan te vinken.

security-namespace
Autorisatie

Wat we in dit bestand hebben geplaatst is de configuratie voor zowel authenticatie als autorisatie. De autorisatie kan je vinden in de

security:http

tags. Hier zeggen we namelijk welke rollen toegang hebben tot bepaalde pagina’s. In dit voorbeeld gaan we alle URLs die beginnen met /admin enkel toegankelijk maken voor gebruikers met de rol ROLE_ADMIN, terwijl gewone gebruikers de toegang krijgen tot URLs die beginnen met /user.

Ook de logout-URL hebben we aangepast. De logout-URL zal gemapt worden op /logout, deze URL wordt door Spring security zelf afgehandeld, hier hebben wij dus geen controle over. Waar we echter wel controle over hebben is de URL die aangeduid is in het

logout-success-url

attribuut. Dit zal de URL zijn waar Spring security naar zal verwijzen als je succesvol uitgelogd bent.

Authenticatie

De volgende stap is dat we nu nog bepaalde gebruikers moeten koppelen aan de aangegeven rollen. Hiervoor gebruiken we de

security:authentication-manager

tag.
In dit eerste voorbeeld hebben we gewoon enkele users/wachtwoorden voorzien voor de gebruikers user en admin. Uiteraard is dit geen goede manier van authenticatie (alles in plain text in je configuratie plaatsen), maar dit is dan ook enkel louter ter illustratie. In de volgende tutorials zullen we meerdere vormen van authenticatie managers zien.

Controller

Nu komen we terug in het bekende MVC verhaal terecht. In de controller (

MainController

) gaan we enkele views voorzien voor een niet geauthenticeerde gebruiker, een gebruiker met de rol ROLE_USER en een gebruiker met de rol ROLE_ADMIN. De code hiervoor is:

package be.g00glen00b.controller;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MainController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String getIndexPage(Principal principal, ModelMap model) {
        if (principal != null) {
            model.addAttribute("name", principal.getName());
        }
        return "index";
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUserPage(Principal principal, ModelMap model) {
        return getIndexPage(principal, model);
    }

    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public String getAdminPage() {
        return "admin";
    }

    @RequestMapping(value = "/logout-success", method = RequestMethod.GET)
    public String getLogoutPage(ModelMap model) {
        model.addAttribute("message", "U bent succesvol uitgelogd.");
        return "index";
    }
}

De meeste code uit deze controller zou je bekend in de oren moeten klinken (als je mijn andere tutorials gevolgd hebt), wat echter wel nieuw is is het

Principal

object. Net zoals de

ModelMap

zal deze automatisch in de Spring controller geïnjecteerd worden.
Dit object voorziet enkele basisgegevens van de ingelogd gebruiker (of

null

indien de gebruiker niet ingelogd is). In deze tutorial gaan we dit object gebruiken om de gebruikersnaam op te vragen met de

principal.getName()

methode.

Views

De laatste stap is natuurlijk dat we ook nog enkele views moeten voorzien. In dit voorbeeld heb ik een

index.jsp

en een

admin.jsp

pagina.

Index pagina

De code voor de index JSP pagina is de volgende:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="sec"
    uri="http://www.springframework.org/security/tags"%>

<html>
<body>
    <ul>
        <li><a href="./">Home</a></li>

        <sec:authorize ifAllGranted="ROLE_ADMIN">
            <li><a href="./admin">Admin</a></li>
        </sec:authorize>
        <sec:authorize ifAllGranted="ROLE_USER">
            <li><a href="./logout">Logout</a></li>
        </sec:authorize>
        <sec:authorize ifNotGranted="ROLE_USER">
            <li><a href="./user">Login</a></li>
        </sec:authorize>
    </ul>

    <c:choose>
        <c:when test="${empty message}">
            <sec:authorize ifNotGranted="ROLE_USER">
                Hallo, gelieve eerst in te loggen!
            </sec:authorize>
            <sec:authorize ifAllGranted="ROLE_USER">
                Hallo ${name}!
            </sec:authorize>
        </c:when>
        <c:otherwise>
            ${message}
        </c:otherwise>
    </c:choose>
</body>
</html>

Wat nieuw is in deze JSP pagina is dat we gebruik maken van de Spring security taglibs om bepaalde informatie te tonen afhankelijk van de rol van de gebruiker.
Zo kunnen we controleren of een gebruiker een bepaalde rol heeft dankzij de volgende tag:

<sec:authorize ifAllGranted="ROLE_USER">
    ...
</sec:authorize>

Om te zien of een gebruiker een bepaalde rol niet heeft kan je dan weer de volgende code gebruiken:

<sec:authorize ifNotGranted="ROLE_USER">
    ...
</sec:authorize>

Admin pagina

De code voor de admin pagina is vrij eenvoudig (enkel als voorbeeld) en is de volgende:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<html>
<body>
    <ul>
        <li><a href="./">Home</a></li>

        <sec:authorize ifAllGranted="ROLE_ADMIN">
            <li><a href="./admin">Admin</a></li>
        </sec:authorize>
        <sec:authorize ifAllGranted="ROLE_USER">
            <li><a href="./logout">Logout</a></li>
        </sec:authorize>
        <sec:authorize ifNotGranted="ROLE_USER">
            <li><a href="./user">Login</a></li>
        </sec:authorize>
    </ul>
    Dit is de admin pagina! Probeer hier maar eens naartoe te gaan als je niet ingelogd bent ;)
</body>
</html>

Builden

Daarmee is dan ook het volledige project afgerond. Builden doen we zoals gewoonlijk met:

mvn clean install

Het project testen zelf kan je doen dankzij de Tomcat plugin:

mvn tomcat:run

Als je dan naar http://localhost:8080/spring-security-example/ navigeert krijg je de volgende pagina te zien:

result-not-logged-in

Als je dan op de login link klikt zal je merken dat je een form te zien krijgt die we helemaal niet zelf gemaakt hebben. Deze form is zoals ik eerder gezegd heb door Spring security voorzien. In één van de volgende tutorials zal je uiteraard ook te zien krijgen hoe je dit zelf kan aanpassen.

result-login

Als je dan inlogt met gebruikersnaam user en wachtwoord user123 dan zal je merken dat je terug gestuurd wordt naar de index pagina, maar dat deze nu een bericht toont met jouw naam (die we via het Principal object te weten zijn gekomen).

Als je daarna uitlogt zal je merken dat je op de logout-success URL terecht bent gekomen.

result-logout

Als je echter inlogt met de gebruikersnaam admin en wachtwoord admin123 zal je merken dat je een extra link te zien krijgt naar de admin-pagina. Dit hebben we mogelijk kunnen maken dankzij de extra Spring security JSP tags.

result-admin

Zoals je kan zien heb je nu dus ook toegang tot de administratie pagina.

result-admin-page

Als laatste stap moet je nu maar eens uitloggen en terug naar de administratie URL gaan. Je krijgt nu natuurlijk niet dezelfde pagina te zien maar hetzelfde loginscherm. Als je nu echter inlogt met een gebruiker die geen toegang heeft tot die pagina (bijvoorbeeld inloggen met gebruiker user) dan zal je merken dat je een 403 error krijgt (dit is een standaard foutmelding voor pagina’s waar je geen toegang toe hebt). In de volgende tutorials zal je ook zien hoe je deze kan gaan aanpassen.

result-denied

Je merkt dus dat Spring security het authenticatie/autorisatie proces zeer eenvoudig maakt. Op basis van wat configuratie hebben we nu een web applicatie kunnen beveiligen. In de komende tutorials zullen we echter de kracht van het Spring security framework echt gaan gebruiken en gaan we authenticatie dmv een database mogelijk maken en in een stap daarna gaan we zelfs onze eigen vorm van authenticatie voorzien. Andere dingen die ik zeker wil laten zien is hoe je het inlog-scherm kan aanpassen of de 403 error die je daarnet te zien kreeg.

Hiermee wil ik dan ook deze eerste tutorial rond Spring security afronden.

Download project

Download – spring-security-example.tar.gz (11 kB)

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.