Spring Security Tutorial

 

Zakładam, że znasz podstawy Spring MVC( Stworzenie dispatchera oraz controllerów ). W tym artykule będziemy konfigurować Springa oraz Spring Security za pomocą języka XML.

Tworzenie projektu



Tworzymy nowy projekt Mavena i jako typ projektu wybieramy Web Application.

Po stworzeniu projektu musimy odnaleźć plik pom.xml. Jest to plik konfiguracyjny projektu charakterystyczny dla Mavena.

Po co teraz pom.xml?

Musimy teraz wskazać zależności projektu, czyli z jakich bibliotek będziemy korzystać.

Znajdujemy sekcje dependiences.

U mnie ona wygląda tak:

<dependencies>

<dependency>

<groupId>javaxgroupId>

<artifactId>javaee-web-apiartifactId>

<version>7.0version>

<scope>providedscope>

dependency>

dependencies>

Znajduje się tutaj jedna zależność (blok dependecy) javaee-web-api. Dodamy teraz niezbędne zależności dla Spring MVC:

<dependency>

<groupId>org.springframeworkgroupId>

<artifactId>spring-coreartifactId>

<version>4.2.0.RELEASEversion>

dependency>


<dependency>

<groupId>org.springframeworkgroupId>

<artifactId>spring-webartifactId>

<version>4.2.0.RELEASEversion>

dependency>


<dependency>

<groupId>org.springframeworkgroupId>

<artifactId>spring-webmvcartifactId>

<version>4.2.0.RELEASEversion>

dependency>


W tej chwili mamy bazowe biblioteki dla Springa MVC. Dodamy teraz dispatcher oraz prosty controller, żeby sprawdzić czy wszystko działa jak należy.

W folderze webapp dodajemy folder WEB-INF w, którym będziemy umieszczać nasze strony oraz pliki konfiguracyjne Springa. Fizyczna struktura projektu powinna być teraz następująca:

Następnie w folderze WEB-INF tworzymy plik web.xml a w nim umieściłem taką zawartość:

version="1.0" encoding="UTF-8"?>

xmlns="http://xmlns.jcp.org/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"

version="3.1">


dispatcher

org.springframework.web.servlet.DispatcherServlet
1
dispatcher
/
30


Teraz tworzymy dispatcher-servlet.xml obok pliki web.xml z jego wstępną zawartością:

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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.3.xsd

">

beans>

Konfiguracja dispatcher-servlet.xml

Teraz określam gdzie będą znajdowały się nasze pliki jsp oraz definiowanie controllerów za pomocą adnotacji.

version="1.0" encoding="UTF-8"?>

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:c="http://www.springframework.org/schema/c"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd

http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd

http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd

">


base-package="com.mycompany.springsecuritytutorial"/>

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

name="prefix" value="/WEB-INF/jsp/"/>

name="suffix" value=".jsp"/>

Pozostaje nam dodanie controllera w pakiecie com.mycompany.springsecuritytutorial.

Dodaję klasę Hello.java a w niej tworzę controller

package com.mycompany.springsecuritytutorial;


import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

@Controller

public class Hello {

@RequestMapping("/index")

public String helloController(Model model) {

return "index";

}

}

Efekt powinien być następujący:

Spring Security zależoności

Teraz dodamy niezbędne biblioteki dla Spring Security

  • spring-security-web

  • spring-security-config


org.springframework.security

spring-security-web

3.2.8.RELEASE

org.springframework.security

spring-security-config

3.2.8.RELEASE

Obok pliku web.xml stworzymy plik spring-secutity.xml.

W tym pliku będziemy umieszczali konfigurację dla zabezpieczeń Springa. Będziemy ustalać kto ma dostęp do czego oraz z jakimi prawami. Bazowy szablon tego pliku jest następujący:

xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="

http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security-3.2.xsd

http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


Mimo iż zdefiniowaliśmy plik konfiguracyjny dla Spring security to spring nie wie, że ma go używać. Poinformujemy go o tym w pliku web.xml.

Przed deklaracją naszego dispatchera wskazujemy gdzie znajduje plik z contextem aplikacji. Dla przypomnienia w Application Context umieszczamy niezbędną konfigurację dla aplikacji. Ponieważ nasza aplikacja może się składać też z innych modułów oprócz samej warstwy web.


contextConfigLocation

/WEB-INF/spring-security.xml

org.springframework.web.context.ContextLoaderListener

Spring Security filtruje adresy url, które mają przejść przez zabezpiecznia springa. Spring dostarcza na ten cel swoje gotowe klasy.


MojFiltr

org.springframework.web.filter.DelegatingFilterProxy

MojFiltr

/*

Powyższy kod oznacza , że będą sprawdzane wszystkie adresy wywoływane przez klienta. Dokładniejszą konfigurację umieścimy w pliku spring-security.xml. Całość pliku web.xml jest następująca:

version="1.0" encoding="UTF-8"?>

xmlns="http://xmlns.jcp.org/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"

version="3.1">

contextConfigLocation

/WEB-INF/spring-security.xml, /WEB-INF/applicationContext.xml

org.springframework.web.context.ContextLoaderListener

dispatcher

org.springframework.web.servlet.DispatcherServlet

1

dispatcher

/

springSecurityFilterChain

org.springframework.web.filter.DelegatingFilterProxy

springSecurityFilterChain

/*

30


Przejdźmy teraz do konfiguracji pliku spring-security.xml

Konfiguracja spring-security.xml

Podstawowo musimy stworzyć dwie sekcje, żeby to działało.

  • http

  • authentication-manager

W pierwszej sekcji (http) będziemy ustalać w jaki sposób ma się odbywać logowanie oraz jakie adresy mają być sprawdzane. W drugiej sekcji (authentication-manager) będziemy definiować dane do logowania oraz role użytkowników. Najpierw wpiszemy użytkownika na “szytywno”, a później zrobimy coś bardziej praktycznego, czyli będziemy uwierzytelniać przez bazę danych. Zaczniemy od konfiguracji sekcji http

Konfiguracja sekcji http

Wprowadźcie poniższy kod do pliku security.

auto-config="true">

pattern="/user/*" access="ROLE_USER" />

Atrybut auto-config=”true” jest skróconym zapisem.


Wszystkie trzy tagi muszą znaleźć się konfiguracji.

  • form-login

Do tego tagu możemy przypisać jaki powinien być adres url do logowania, gdzie powinno nastąpić przekierowanie po udanym zalogowaniu, oraz gdzie ma odesłać użytkownika w przypadku błędu przy logowaniu. Przykładowa konfiguracja tych atrybutów

<form-login

</form-login
login-page='/login.do'

default-target-url="/start.do"

authentication-failure-url="/login.do?error=true" />

  • login-page - własny adres do logowania

  • default-target-url - przekierowanie w przypadku udanego logowania

  • authentication-failure-url - przekierowanie w przypadku błędu

  • http-basic

Jeżeli jest zdefiniowany tag form-login to Spring nie będzie brał pod uwagę tagu http-basic. Jeżeli w naszej konfiguracji nie będzie form-login to logowanie odbędzie się przez przeglądarkę i będzie to wyglądało w ten sposób:

  • logout

Ten tag służy do wylogowania się. Można w nim dodać atrybut logout-success-url, w którym ustalamy gdzie przekierujemy użytkownika po pomyślnym wyologowaniu się.

logout-success-url="/login.do" />

My zostaniemy przy domyślnej konfiguracji więc nasz plik spring-security.xml wygląda w ten sposób:

xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="

http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security-3.2.xsd

http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

auto-config="true">

Intercept-url

Za pomocą tagu intercept-url będziemy ustalali kto ma dostęp do jakiego adresu url. Ustalmy, że w bierzącej chwili będziemy wymagali logowania dla wszystkich adresów zaczynających się od /user**

auto-config="true">

pattern="/user/*" access="ROLE_USER" />

W atrybucie pattern określamy adres url, a w atrybucie access wymagane uprawnienia.

Konfiguracja sekcji authentication-manger

Teraz jeszcze musimy definiować użytkowników dla naszej aplikacji:

name="user" password="user123" authorities="ROLE_USER" />

W tej chwili interesuje nas sekcja user-service, w której definiujemy użytkowników. Sprawdźmy teraz czy wszystko działa, wobec tego w pliku Hello.java dodajmy controller, który będzie wymagał autoryzacji.

Zwróćcie uwagę na strukture katalogów. W folderze jsp dodaliśmy folder user a nim plik index.jsp

Po odpaleniu aplikacji i wejściu np na taki url: http://localhost:9090/SpringSecurityTutorial/user/ powinno was przekierować na http://localhost:9090/SpringSecurityTutorial/spring_security_login

i efekt będzie następujący :

Teraz spróbujmy się zalogować za pomocą danych

Login: user

Password: abc

Logowanie oczywiście zakończy się nie powodzeniem i dostaniemy taki efekt:

Teraz wpiszmy poprawne dane do logowanie , czyli takie jak zdefiniowaliśmy w pliku spring-security.xml i bez problemu powinniśmy być zalogowani i pomyślnie przekierowani.

Warto też zwrócić uwagę, że Spring umieścił ciasteczko z identyfikatorem naszej sesji.

Dodajmy teraz dodatkowego użytkownika z rolą admina (ROLE_ADMIN). Pamiętajmy też o dodaniu dodatkowego intercept-url, o dodatkowym controllerze oraz widoku.

Najpierw dodajemy wpisy do spring-security.xml

auto-config="true">

pattern="/user/*" access="ROLE_USER" />

pattern="/admin/*" access="ROLE_ADMIN" />

name="user" password="user123" authorities="ROLE_USER" />

name="admin" password="admin1" authorities="ROLE_ADMIN" />

Potem dodajemy dodatkowy Controller:

@RequestMapping("/admin/*")

public String admin(Model model) {

return "admin/index";

}

Oraz dodajemy folder na widoku. Oczywiście wszystko może być w jednym folderze, ale dbamy o porządek w naszej aplikacji.

Wejdźmy teraz na http://localhost:9090/SpringSecurityTutorial/admin/ i spróbujmy się zalogować jako zwykły user.

Dostajemy 403 czyli brak uprawnień. Usuńmy teraz nasz cookie od Springa albo zrestartujmy server. Następnie zalogujmy się jako admin:

Własny formularz logowania

Zaczniemy od modyfikacji pliku spring-security.xml:

auto-config="true">


login-page="/login"

authentication-failure-url="/login?error"

username-parameter="username"

password-parameter="password"

/>

pattern="/user/*" access="ROLE_USER" />

pattern="/admin/*" access="ROLE_ADMIN" />

Dodałem tutaj dwa dodatkowe atrybuty to from-login:

  • username-parameter - nazwa pola użytkownika z kodu html

  • password-parameter - nazwa pola hasła z kodu html

Wskazaliśmy springowi, że logowanie odbędzie poprzez adres /login. Wobec tego stwórzmy controller, który będzie obsługiwał te żądanie:

@RequestMapping(value = "/login", method = RequestMethod.GET)

public ModelAndView login(

@RequestParam(value = "error", required = false) String error,

@RequestParam(value = "logout", required = false) String logout) {

ModelAndView model = new ModelAndView("login");

if (error != null) {

model.addObject("error", "Nie poprawna nazwa użytkownika lub hasło");

}

if (logout != null) {

model.addObject("logout", "Pomyślnie wylogowano!");

}

return model;

}

Jak widać musimy obsłużyć tutaj dwa przychodzące parametry.

  • error - jeżeli nastąpi błąd przy logowaniu

  • logout - jeżeli ktoś się wyloguje

Są to zwykłe zmienne tekstowego dlatego wprowadzę tutaj polskie nazwy komunikatów i prześlę je na widok.

Teraz w folderze jsp utwórzmy plik login.jsp z następującą zawartością:

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

http-equiv="Content-Type" content="text/html; charset=UTF-8">

class="loginbox">

Proszę się zalogować:


test="${not empty error}">

class="error">${error}

name='loginForm'

action="" method='POST'>

Login: type="text" name="username"/>


Hasło: type="password" name="password" />


name="submit" type="submit" value="submit" />

W bloku div z klasą (css) loginbox sprawdzam sobie czy mam do wyświetlenie komunikat o błędzie. Jeżeli tak to go wyświetlam.

test="${not empty error}">

class="error">${error}

Drugą ważną rzeczą jest ustawienie docelowego adresu url formularza. Docelowy url dostajemy od springa w postaci zmiennej.

Musimy pamiętać aby ustawić odpowiedni atrybut name dla pól formularza. W tym przypadku jest to username i password tak jak ustaliliśmy to w pliku spring-security.xml

username-parameter="username"

password-parameter="password"

Pozostaje tam dodanie biblioteki tagów jstl do pliku pom.xml. Dodajmy tą zależoność:


jstl

jstl

1.1.0

Teraz pozostaje nam zbudować projekt ze i go odpalić.

Po wejściu na http://localhost:9090/SpringSecurityTutorial/user/

W przypadku błędnych danych do logowania:

Po poprawnym zalogowaniu:

Integracja Spring Security z bazą danych

W tym artykule będziemy korzystali z bazy danych Oracle, jednak nic nie stoi na przeszkodzie abyście korzystali z innych baz takich jak MySQL czy MSSQL.

1.Ściągniecie sterownika do komunikacji z bazą danych

Uzupełniamy nasz pom.xml o dodatkową zależność czyli nasz sterownik do bazy danych

com.oracle

ojdbc6

11.2.0.3

2. Stworzenie kontekstu aplikacji

W następnej kolejności w pliku web.xml uzupełniamy sekcję context-param o dodatkowy plik. Powinno to teraz wyglądać tak:

contextConfigLocation

/WEB-INF/spring-security.xml, /WEB-INF/applicationContext.xml

Czyli dopisaliśmy dodatkowy plik z contextem aplikacji. W kontekście aplikacji umieszczamy wszelkie beany ze Springa. Możecie zauważyć, że w pliku dispatcher-servlet.xml umieściliśmy już beana i czemu by nie spróbować tam wstawiać innych beanów. Otóż plik dispatcher-servlet.xml jest oznaczony jako zwykły servlet tam nie ustawiamy konfiguracji całej aplikacji, tylko informujemy m. in. o położeniu plików jsp.

3. Stworzenie pliku applicationContext.xml

Teraz stworzymy nasz kontekst aplikacji w folderze WEB-INF.

Całość powinna wyglądać w ten sposób:

version="1.0" encoding="UTF-8"?>

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsd

">

id="dataSource" class="oracle.jdbc.pool.OracleDataSource">

name="dataSourceName" value="ds"/>

name="URL" value="jdbc:oracle:thin:@localhost:1521:xe"/>

name="user" value="szkolenie"/>

name="password" value="haslo" />

Przed stworzeniem beana sugeruję zbudowanie projektu wraz z zależnościami. Dostaniemy wtedy podpowiedzi przy tworzeniu beana.

Tworzenie beana zostało pogrubione. Musimy wskazać jakie typu będzie nasz bean (atrybut class), oraz ustawić jego identyfikator, czyli nazwę po której będziemy się do niego odwoływać (atrybut id).

Następnie ustawiamy właściwości połączenia, czyli

  • URL - adres z jakim się łączymy. W tym przypadku jest to localhost na porcie 1521 a sid bazy to xe

  • user - nazwa użytkownika w bazie danych. W tym przypadku szkolenie

  • password - hasło użytkownika do bazy danych.

UWAGA! Nazwy właściwości takie user mogą się różnić w zależności od używanej klasy (w tym przypadku jest to: oracle.jdbc.pool.OracleDataSource ). Dla innych sterowników pole user może nazywać się username

4. Utworzenie odpowiednich tabelek w bazie danych

Spring Security do logowania wykonuje dwa zapytania. Pierwsze czy dane do logowania są poprawne, a drugie o uprawnienia użytkownika.

Stworzyłem w bazie następującą tabelkę dla użytkowników:

Wymagane pola to : login,pass,aktywny. Spring zawsze sprawdza czy użytkownik został aktywowany.

Stworzyłem też drugą tabelkę z uprawnieniami użytkowników:

Umieściłem w niej informacje o identyfikatorze użytkownika i o jego roli.

Teraz czas na dane dla tabeli użytkownicy:

Dane dla tabeli uprawnienia:

Zwróćcie uwagę że role są zapisane zgodnie z rolami Spring Security.

5. Aktualizacja spring-security

Teraz pozostaje nam aktualizacja pliku spring-security.xml. Wyrzucamy nasz sztywno stworzony user-service i dodajemy jdbc-user-service. Powinno to wyglądać w ten sposób:

<authentication< span="">-manager>

<authentication< span="">-provider>

<jdbc< span="">-user-service data-source-ref="dataSource"

users-by-username-query=

"select login,pass, aktywny from users where login=?"

authorities-by-username-query=

"select login, rola from uprawnienia ur join users u on u.user_id = ur.user_id where login =? " />

</jdbc<></authentication<>-provider>

</authentication<>-manager>

  • data-source-ref - podajemy tam identyfikator naszego beana z połączeniem do bazy danych. U nas nazywał się od dataSource.

  • users-by-username-query - To zapytanie pobiera login, hasło oraz status aktywności użytkownika

  • authorities-by-username-query - To zapytanie sprawdza uprawnienia użytkownika. Uwaga! Spring zawsze potrzebuje tutaj loginu oraz roli użytkownika dlatego musimy tutaj joinować.

Co jest ważne w tych zapytaniach?

Zapytanie users-by-username-query mu zawsze zwracać 3 kolumny; gdzie pierwsza to login, druga to hasło, a trzecia to aktywność użytkownika. Te dane możecie mieć rozbite po kilku tabelach, jednak waszym zadaniem jest napisanie takiego zapytania, które zwróci konkretnie takie trzy a nie inne kolumny.

Jeżeli na przykład nie zależy wam na aktywności użytkowników to zamiast kolumny aktywny wstawili byśmy po prostu jeden.

select login,pass, ‘1’ from users where login=?

Adekwatna sytuacja z drugim zapytaniem. Musiscie dostarczyć springowi login oraz rolę użytkownika. A gdzie są dane i jak są rozmieszczone to wasza sprawa.

Po odpaleniu efekt logowania powinien być ten sam. Natomiast logujemy się teraz poprzez bazę danych.