107
Ak.god. 2015./2016. Preddiplomski projekt Preddiplomski projekt 9. REST - poslužiteljski dio u Springu Zaštićeno licencom http://creativecommons.org/licenses/by-nc-sa/2.5/hr/

9. predavanje [1,26 MiB]

Embed Size (px)

Citation preview

Page 1: 9. predavanje [1,26 MiB]

Ak.god. 2015./2016.

Preddiplomski projekt

Preddiplomski projekt

9. REST - poslužiteljski dio u Springu

Zaštićeno licencom http://creativecommons.org/licenses/by-nc-sa/2.5/hr/

Page 2: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Creative Commons

■ slobodno smijete: l dijeliti — umnožavati, distribuirati i javnosti priopćavati djelo l remiksirati — prerađivati djelo

■ pod sljedećim uvjetima: l imenovanje. Morate priznati i označiti autorstvo djela na način kako je

specificirao autor ili davatelj licence (ali ne način koji bi sugerirao da Vi ili Vaše korištenje njegova djela imate njegovu izravnu podršku).

l nekomercijalno. Ovo djelo ne smijete koristiti u komercijalne svrhe. l dijeli pod istim uvjetima. Ako ovo djelo izmijenite, preoblikujete ili

stvarate koristeći ga, preradu možete distribuirati samo pod licencom koja je ista ili slična ovoj.

U slučaju daljnjeg korištenja ili distribuiranja morate drugima jasno dati do znanja licencne uvjete ovog djela. Najbolji način da to učinite je linkom na ovu internetsku stranicu. Od svakog od gornjih uvjeta moguće je odstupiti, ako dobijete dopuštenje nositelja autorskog prava. Ništa u ovoj licenci ne narušava ili ograničava autorova moralna prava.

Tekst licencije preuzet je s http://creativecommons.org/.

218.5.2016.

Page 3: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sadržaj predavanja

♦ Spring Boot ♦ Dizajniranje usluge ♦ Implementacija REST-a u Springu ♦ Sigurnost

318.5.2016.

Page 4: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Spring - http://spring.io

♦ radni okvir za lakši razvoj aplikacija ♦ bavi se konfiguracijom objekata u sustavu ♦ upravlja poslovnim objektima kao običnim objektima (POJO –

plain old java objects) ♦ brine se za kreiranje objekata (IoC – inversion of control) ♦ povezuje kreirane objekte (IoC, wiring up, dependency injection) ♦ upravlja njihovim životnim ciklusom ♦ složene veze između objekata se definiraju u XML-u ili pomoću

bilješki (annotation) ♦ odvaja poslovnu logiku od mehanizama za ispravan rad sustava

(transakcije, logiranje, ...) ♦ vrlo je složen za početnika jer ima puno stvari ugrađeno

418.5.2016.

Page 5: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Arhitektura Spring Frameworka - dokumentacija

518.5.2016.

Page 6: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Spring Boot

♦ jedan od podprojekata Springa ! http://projects.spring.io/spring-boot/

♦ pojednostavnjuje korištenje Springa ♦ podržava:

! automatsku konfiguraciju ! pretraživanja klasa na putu (path)

♦ aplikacija ima manje koda ♦ kod web-aplikacija - omogućuje izradu samostalnih aplikacija

! web-poslužitelj zapakiran u jar ! jednostavnije instaliranje ! aplikacija spremna za produkcijsku okolinu

♦ primjeri projekata618.5.2016.

Page 7: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer: lectures

♦ popis nastavnika (osoba) i njihovih predmeta ♦ dohvaćanje i spremanje u bazu

♦ prikaz u JSON-u (REST) ! uređivanje (dodavanje, promjena, brisanje) podataka

718.5.2016.

Page 8: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Stvaranje Spring Boot projekta

♦ otvoriti stranicu https://start.spring.io ♦ klik na "Switch to the full version" ♦ odabrati: Gradle Project ♦ popuniti:

! Group: hr.fer.tel.ruazosa ! Artifact: lectures ! Name: lectures ! Packaging: Jar ! Java Version: 1.8 ! Language: Java

! odabrati: Web, JPA, H2, DevTools, Rest Repositories ! opcionalno: Actuator, HATEOAS, REST Docs ! klik na Generate Project ! Android studio ➔ import Gradle project

818.5.2016.

Page 9: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Spring Boot projekt

♦ struktura projekta ♦ pogledati:

! klasa LecturesApplication " klasa koja se pokreće

! datoteka build.gradle " skripta za "građenje"

! datoteka application.properties " vanjska konfiguracija

918.5.2016.

Page 10: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Spring Boot DevTools

♦ Kod razvoja ubrzavaju ponovno učitavanje resursa i klasa ! odabrati kod kreiranja DevTools ili ! dodati knjižnicu u build.gradle: compile('org.springframework.boot:spring-boot-devtools')

♦ Ponovno učitavanje resursa iz preglednika ! treba instalirati plugin u preglednik: http://livereload.com/extensions/

♦ više na: https://spring.io/blog/2015/06/17/devtools-in-spring-boot-1-3

1018.5.2016.

Page 11: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Obrada HTTP zahtjeva u Spring Web MVC-u

Slika preuzeta sa http://docs.spring.io/autorepo/docs/spring/3.2.x/spring-framework-reference/html/mvc.html

1118.5.2016.

Page 12: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Arhitektura web-aplikacije (REST)

1218.5.2016.

sloj pristupapodacima

sloj poslovnelogike

@Service:PersonService

repository:PersonRepository

4: dohvaća i sprema podatke

@Entity:Person

sloj usluge

web-poslužitelj (REST)

1: HTTP zahtjev

2: obradi(HttpRequest)

7: HTTP odgovor

@RestController:PersonController

3: poziva

Jacksonmapping

5: p

rosli

jedi

obr

adu

(Per

sonR

epre

sent

atio

n)

6: JSON ili XML

PersonRepresentation

@Entity:Person

Page 13: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA 13

Oblikovni obrazac MVC Model View Controller

18.5.2016.

Page 14: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

MVC

♦ Model ! entiteti ! preslikani podaci iz baze u objekte u Javi

♦ View - pogled ! prikazuje podatke entiteta u obliku HTML-a, JSON-a, XML-a, ... ! HTML stranica tj. Thymeleaf predlošci

♦ Controller - kontroler ! Spring kontroler ! zahtjev dolazi na njega ! koristi uslugu (service) da koja poziva repozitorij za učitavanje/

spremanje podataka (u/iz entiteta u podatke u bazu i obrnuto)

1418.5.2016.

Page 15: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA 15

JPA Java Persistance API

18.5.2016.

Page 16: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Java Persistence API - JPA

♦ specifikacija JPA 2.0 - JSR 317 ♦ omogućuje preslikavanje, spremanje promjenu i dohvaćanje

podataka iz/u objekata u/iz bazu/e podataka ♦ popularne implementacije:

!Hibernate (podrazumijevano se koristi u Springu) !EclipseLink - referentna implementacija !Apache OpenJPA

♦ entitet je klasa koja se može spremiti u bazu podataka !mora biti označena bilješkom

♦ koristi se Java Persistence Query Language (JPQL) ! sličan SQL-u

1618.5.2016.

Page 17: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Bilješke (javax.persistence) - tutorial

♦ atributi ili metode get/set " Ne smije se miješati označavanje atributa i metoda. Treba odabrati samo jedan način u jednoj

klasi. ! @Column(name="newColumnName") - označavanje kolone ! @Id - jedinstveni identifikator ! @GeneratedValue - zajedno s @Id označava da je vrijednost generirana

automatski ! @Transient - atribut neće biti spremljen u bazu ! @NotNull - ne smije biti null ! @ElementCollection(fetch=EAGER ili LAZY) - označava kolekcije

♦ klasa ! @Entity - označava da je klase entitet koji se preslikava

♦ relacije ! @OneToOne(mappedBy="attributeOfTheOwningClass")! @OneToMany(mappedBy="attributeOfTheOwningClass")! @ManyToOne! @ManyToMany

1718.5.2016.

Page 18: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa Person - entitet

package hr.fer.tel.ruazosa.entities;

@Entity

public class Person {

@Id @GeneratedValue

private Long id;

private String firstName, lastName, phone, room;

public Person() { }

... // konstruktori, set i get metode,

// equals i hashcode za id

1818.5.2016.

Page 19: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonRepository

package hr.fer.tel.ruazosa.repositories;

// Person je objekt, a Long je ključ

public interface PersonRepository extends

CrudRepository<Person, Long> {

}

1918.5.2016.

Page 20: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa LecturesApplication - ubacivanje inicijalnih

@SpringBootApplicationpublic class LecturesApplication implements CommandLineRunner { @Autowired private PersonRepository repo;

// metoda koja sve pokreće public static void main(String[] args) { SpringApplication.run(LecturesApplication.class, args); }

// inicijalizacija podataka @Override public void run(String... args) throws Exception { Person p = new Person("Pero", "Perić", "1111", "C15-15"); repo.save(p); p = new Person("Iva", "Ivić", "555", "C0-32"); repo.save(p); }

2018.5.2016.

Page 21: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

H2 konzola

♦ na poveznici http://localhost:8080/h2-console ! imamo konzolu za pregledavanje baze podataka ! spajamo se na: jdbc:h2:mem:testdb

2118.5.2016.

Page 22: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Deregistracija dostupnosti repozitorija

♦ repozotoriji su objavljeni na adresama: ! /{repository} - GET, POST ! /{repository}/{id} - GET, PUT, DELETE, PATCH

♦ promjena URL-a za REST: spring.data.rest.basePath=/rest

♦ deregistracija (exported = false): package hr.fer.tel.ruazosa.repositories;

import org.springframework.data.repository.CrudRepository;import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(exported = false)public interface PersonRepository extends CrudRepository<Person, Long> {}

2218.5.2016.

Page 23: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sučelje PersonService

package hr.fer.tel.ruazosa.services; import hr.fer.tel.ruazosa.entities.Person; public interface PersonService { public Iterable<Person> findAll(); public Person getWithId(Long id); public Person save(Person convertToEntity); public void update(Person person); public void delete(String id); }

2318.5.2016.

Page 24: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonServiceJpaImpl (1)

package hr.fer.tel.ruazosa.services.jpa;

... import javax.transaction.Transactional; @Service public class PersonServiceJpaImpl implements PersonService { private PersonRepository repo; @Autowired public PersonServiceJpaImpl(PersonRepository repo) { this.repo = repo; } @Override public Iterable<Person> findAll() { return repo.findAll(); }

2418.5.2016.

Page 25: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonServiceJpaImpl (2)

@Override public Person getWithId(Long id) { return repo.findOne(id); } @Override @Transactional public Person save(Person person) { return repo.save(person); } @Override @Transactional public void update(Person person) { Person oldPerson = repo.findOne(person.getId()); if(oldPerson == null) throw new IllegalArgumentException( "Person does not exist."); repo.save(person); }

2518.5.2016.

Page 26: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonServiceJpaImpl (3)

@Override @Transactional public void delete(String id) { repo.delete(Long.parseLong(id)); } }

2618.5.2016.

Page 27: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Eksterna baza podataka - postgres

♦ pokrenuti bazu i pomoću pgAdmina stvoriti bazu s imenom lectures

♦ u gradle.build zamijeniti H2 s PostreSQL-om ...dependencies {... //runtime('com.h2database:h2') runtime('org.postgresql:postgresql')}...

♦ u application.properties dodati spring.datasource.url= jdbc:postgresql://localhost:5432/lecturesspring.datasource.username=postgresspring.datasource.password=

# none, validate, update, create, create-dropspring.jpa.hibernate.ddl-auto=create

2718.5.2016.

Page 28: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dizajniranje usluge

♦ paziti na mrežu - uvijek misliti na mrežene stvari kod izrade ♦ napraviti URL za svaki resurs ♦ definirati metode za svaki resurs ♦ stavljati poveznice u resursima

! koristiti XML ili JSON (JavaScript Object Notation) ! ne vraćati čitavu strukturu

♦ specificirati format za svaki resurs ♦ povezati usluge tako da sve kreće preko jednog URL-a

! HATEOAS - Hypermedia as the Engine of Application State ♦ preko vrsta medija dogovarati verzije usluga (API-ja)

2818.5.2016.

Page 29: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Tipično korištenje usluga REST

♦ za Create, Read, Update and Delete (CRUD)

♦ primjer gotove usluge: Twitter

2918.5.2016.

HTTP CRUDPOST Create, (Overwrite/Replace)GET ReadPUT Update, (Create, Delete)DELETE DeletePATCH Partial update

Page 30: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Tablica dizajna usluge (persons)

3018.5.2016.

resurs podržane metode

šalje svrha

/persons GET vraća listu osobaPOST osoba stvara novu osobu

/persons/{id} GET vraća osobu s ID-omDELETE briše osobuPUT osoba mijenja podatke o osobi

Page 31: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/persons ♦ odgovor: 200 OK[

{

"id": 1,

"name": "Pero Perić"

},

{

"id": 2,

"name": "Iva Ivić"

}

]

3118.5.2016.

Page 32: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/persons ♦ sadržaj zahtjeva:

{ "firstName": "Jura", "lastName": "Jurić", "room": "3333", "phone": "C17-01"}

♦ odgovor: 201 CreatedLocation: http://localhost:8080/api/persons/3

3218.5.2016.

Page 33: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/persons/2 ♦ odgovor: { "id": 1, "firstName": "Pero", "lastName": "Perić", "room": "C15-15", "phone": "1111"}

3318.5.2016.

Page 34: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/persons/100 ♦ odgovor: 404 Not Found

♦ sadržaj nije bitan

3418.5.2016.

Page 35: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ PUT /api/persons/2 { "firstName": "Perica", "lastName": "Perić", "room": "1111", "phone": "C15-15"}

♦ odgovor: 204 No Content

3518.5.2016.

Page 36: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ PUT /api/persons/100 { "firstName": "Perica", "lastName": "Perić", "room": "1111", "phone": "C15-15"}

♦ odgovor: 304 Not Modified ETag: Person does not exist.

3618.5.2016.

Page 37: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ DELETE /api/persons/2 ♦ odgovor: 204 No Content

3718.5.2016.

Page 38: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ DELETE /api/persons/100 ♦ odgovor: 404 Not Found

3818.5.2016.

Page 39: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonController (v1)

package hr.fer.tel.ruazosa.controllers; ...@RestController @RequestMapping(value = "/api/persons") public class PresonController { private PersonService service; @Autowired public PresonController(PersonService service) { this.service = service; } @RequestMapping(value = "/{personId}", method = RequestMethod.GET) public PersonResource getPerson( @PathVariable("personId") Long personId) { return new PersonResource(); } }

3918.5.2016.

Page 40: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonResource

package hr.fer.tel.ruazosa.representations; public class PersonResource { private Long id; private String firstName, lastName, room, phone; // metode set i get}

4018.5.2016.

Page 41: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonController (v2)

public class PresonController { ... private PersonAssembler assembler; @Autowired public PresonController(PersonService service, PersonAssembler assembler) { this.service = service; this.assembler = assembler; } @RequestMapping(value = "/{personId}", method = RequestMethod.GET) public PersonResource getPerson( @PathVariable("personId") Long personId) { Person person = throwExceptionIfNull( service.getWithId(personId)); return assembler.toResource(person); }

4118.5.2016.

Page 42: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa RepresentationUtility

package hr.fer.tel.ruazosa.representations; import org.springframework.data.rest.webmvc.ResourceNotFoundException;public class RepresentationUtility { public static <R> R throwExceptionIfNull(R r) { if(r == null) throw new ResourceNotFoundException(); return r; } }

4218.5.2016.

Page 43: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonAssembler

package hr.fer.tel.ruazosa.representations; import org.springframework.beans.BeanUtils; ... @Service public class PersonAssembler { public PersonResource toResource(Person person) { PersonResource resource = new PersonResource(); BeanUtils.copyProperties(person, resource); return resource; } }

4318.5.2016.

Page 44: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonsController (v3)

public class PresonController { ... @RequestMapping(method = RequestMethod.GET) public List<ShortPersonResource> getPersons() { return assembler.toListResource(service.findAll()); } ...

4418.5.2016.

Page 45: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonAssembler

public class PersonAssembler { ... public List<ShortPersonResource> toListResource( Iterable<Person> persons) { List<ShortPersonResource> resources = new LinkedList<ShortPersonResource>(); for(Person p: persons) resources.add(toShortResource(p)); return resources; } public ShortPersonResource toShortResource(Person person) { ShortPersonResource resource = new ShortPersonResource(); resource.setId(person.getId()); resource.setName(person.getFirstName() + " " + person.getLastName()); return resource; }

4518.5.2016.

Page 46: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa ShortPersonResource

package hr.fer.tel.ruazosa.representations; public class ShortPersonResource { private Long id; private String name; // metode get i set}

4618.5.2016.

Page 47: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonController (v4)

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;... public class PresonController { ... @RequestMapping(method = RequestMethod.POST) public ResponseEntity newPerson( @RequestBody PersonResource personResource) { Person person = service.save(assembler.toEntity(personResource)); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.LOCATION, linkTo( methodOn(PresonController.class).getPerson(person.getId()) ).toString()); ResponseEntity response = new ResponseEntity(headers, HttpStatus.CREATED); return response; } }

4718.5.2016.

Page 48: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PersonAssembler

public class PersonAssembler { ... public Person toEntity(PersonResource personResource) { Person person = new Person(); BeanUtils.copyProperties(personResource, person); return person; }}

4818.5.2016.

Page 49: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PresonController (v5)

public class PresonController { ... @RequestMapping(value = "/{personId}", method = RequestMethod.PUT) public ResponseEntity updatePerson( @PathVariable("personId") Long id, @RequestBody PersonResource personResource) { try { Person person = assembler.toEntity(personResource); person.setId(id); service.update(person); return ResponseEntity.noContent().build(); } catch (IllegalArgumentException e) { return ResponseEntity.status(HttpStatus.NOT_MODIFIED) .eTag(e.getMessage()) .build(); } } }

4918.5.2016.

Page 50: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa PresonController (v6)

public class PresonController { ... @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public ResponseEntity deletePerson( @PathVariable("id") String id) { service.delete(id); return ResponseEntity.noContent().build(); } }

5018.5.2016.

Page 51: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dodavanje predmeta u model

5118.5.2016.

firstName:StringlastName:Stringroom:Stringphone:String

Person Course

name:Stringdescription:String

teachingCoursesteacher

*1

enrolledCoursesstudents

*1..*

Page 52: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Klasa Course

@Entity public class Course { @Id @GeneratedValue private Long id; private String name, description; @ManyToOne private Person teacher; @ManyToMany private Set<Person> students;

// metode get i set i konstruktori (inicijalizirati skup)}

5218.5.2016.

Page 53: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Promjene u klasi Person

@Entity public class Person { @Id @GeneratedValue private Long id; private String firstName, lastName, phone, room; @OneToMany(mappedBy = "teacher") private Set<Course> teachingCourses; @ManyToMany(mappedBy = "students") private Set<Course> enrolledCourses;

// u konstruktor dodati inicijalizaciju skupova...}

5318.5.2016.

Page 54: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Baza podataka

5418.5.2016.

Page 55: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sučelje CourseRepository

public interface CourseRepository extends CrudRepository<Course, Long> { List<Course> findByName(String name);}

5518.5.2016.

Page 56: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Promjene u inicijalizaciji (1)

public class LecturesApplication implements CommandLineRunner { ...

@Autowired private CourseRepository crepo;

... @Override

@Transactional public void run(String... args) throws Exception {

Course c = new Course("RUAZOSA", "Razvoj usluga i aplikacija za operacijski sustav Android");

crepo.save(c);

c = new Course("KP", "Konkurentno programiranje"); crepo.save(c);

c = new Course("RASSUS", "Raspodijeljeni sustavi"); crepo.save(c);

c = new Course("OOP", "Objektno orijentirano programiranje"); crepo.save(c);

...

5618.5.2016.

Page 57: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Promjene u inicijalizaciji (2)

...Person p = new Person("Pero", "Perić", "1111", "C15-15"); c = crepo.findByName("RUAZOSA").get(0); p.getTeachingCourses().add(c); c.setTeacher(p); repo.save(p);

p = new Person("Iva", "Ivić", "555", "C0-32"); p.getEnrolledCourses().add(c); c.getStudents().add(p); repo.save(p);

p = new Person("Jura", "Jurić", "333", "C3-81"); p.getEnrolledCourses().add(c); c.getStudents().add(p); repo.save(p);

} }

5718.5.2016.

Page 58: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Tablica dizajna usluge (courses)

5818.5.2016.

resurs podržane metode

šalje svrha

/courses GET vraća listu predmetaPOST predmet stvara novi predmet

/courses/{cid} GET vraća predmet s ID-omDELETE briše predmetPUT predmet mijenja podatke o predmetu

/courses/{cid}/enrollPerson/{pid}

POST upisuje osobu na predmet

/courses/{cid}/unenrollPerson/{pid}

POST ispisuje osobu s predmeta

/courses/{cid}/students

GET vraća popis studenata

Page 59: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/courses ♦ odgovor: 200 OK[

{ "id": 1, "name": "RUAZOSA" },

{ "id": 2, "name": "KP" },

{ "id": 3, "name": "RASSUS" },

{ "id": 4, "name": "OOP" }

]

5918.5.2016.

Page 60: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/courses/1 ♦ odgovor: { "id": 1, "name": "RUAZOSA", "description": "Razvoj usluga i aplikacija za operacijski sustav Android", "teacher": { "id": 1, "name": "Pero Perić" }}

6018.5.2016.

Page 61: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/courses/2 ♦ odgovor: { "id": 2, "name": "KP", "description": "Konkurentno programiranje", "teacher": null}

6118.5.2016.

Page 62: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/courses/100 ♦ odgovor: 404 Not Found

♦ sadržaj nije bitan

6218.5.2016.

Page 63: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses ♦ sadržaj zahtjeva:

{ "name": "DL", "description": "Digitalna logika", "teacherId": 2}

♦ odgovor: 201 CreatedLocation: http://localhost:8080/api/courses/5

6318.5.2016.

Page 64: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses ♦ sadržaj zahtjeva:

{ "name": "DL", "description": "Digitalna logika", "teacherId": null}

♦ odgovor: 201 CreatedLocation: http://localhost:8080/api/courses/5

6418.5.2016.

Page 65: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses ♦ sadržaj zahtjeva:

{ "name": "DL", "description": "Digitalna logika", "teacherId": -1}

♦ odgovor: 400 Bad Requestsadržaj nije bitan

6518.5.2016.

Page 66: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ PUT /api/courses/5 { "name": "DIGLOG", "description": "Digitalna logika", "teacherId": 2}

♦ odgovor: 204 No Content

6618.5.2016.

Page 67: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ PUT /api/courses/100 { "name": "DIGLOG", "description": "Digitalna logika", "teacherId": 2}

♦ odgovor: 304 Not Modified ETag: Course does not exist.

6718.5.2016.

Page 68: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ DELETE /api/courses/2 ♦ odgovor: 204 No Content

6818.5.2016.

Page 69: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ DELETE /api/courses/100 ♦ odgovor: 404 Not Found

6918.5.2016.

Page 70: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses/2/enrollPerson/3 ♦ odgovor: 204 No content

7018.5.2016.

Page 71: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses/1/enrollPerson/1 ♦ odgovor: 400 Bad request u sadržaju je opis greške

7118.5.2016.

Page 72: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses/2/unenrollPerson/3 ♦ odgovor: 204 No content

7218.5.2016.

Page 73: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/courses/1/unenrollPerson/1 ♦ odgovor: 400 Bad request u sadržaju je opis greške

7318.5.2016.

Page 74: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ GET /api/courses/1/students ♦ odgovor: [ { "id": 2, "name": "Iva Ivić" }, { "id": 3, "name": "Jura Jurić" }]

7418.5.2016.

Page 75: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Tablica dizajna usluge (persons)

7518.5.2016.

resurs podržane metode

šalje svrha

/persons GET vraća listu osobaPOST osoba stvara novu osobu

/persons/{pid} GET vraća osobu s ID-omDELETE briše osobuPUT osoba mijenja podatke o osobi

/persons/{pid}/teachingCourses

GET vraća listu predmeta koje predaje

/persons/{pid}/enrolledCourses

GET vraća listu predmeta koje je upisla osoba

Page 76: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/persons/1/teachingCourses ♦ odgovor: 200 OK [ { "id": 1, "name": "RUAZOSA" }, { "id": 2, "name": "KP" }]

7618.5.2016.

Page 77: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

♦ POST /api/persons/1/enrolledCourses ♦ odgovor: 200 OK [ { "id": 2, "name": "KP" }, { "id": 1, "name": "RUAZOSA" }]

7718.5.2016.

Page 78: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dohvaćanje liste predmeta

♦ potrebno je napraviti: ! uslugu i implementaciju - klase: CourseService, CourseServiceJpaImpl

! kratku prezentaciju resursa - klasa ShortCourseResource ! stvaranje liste kratkih resursa - klasa CourseAssembler ! kontroler koji obrađuje zahtjev - klasa CourseController

7818.5.2016.

Page 79: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dohvaćanje predmeta - CourseController

public class CourseController { ... @RequestMapping(value = "/{courseId}", method = RequestMethod.GET) public CourseResource getCourse( @PathVariable("courseId") Long courseId) { Course course = throwExceptionIfNull( courseService.getWithId(courseId)); return courseAssembler.toResource(course); } }

7918.5.2016.

Page 80: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dohvaćanje predmeta - CourseAssembler

@Service public class CourseAssembler { private PersonAssembler personAssembler; @Autowired public CourseAssembler(PersonAssembler personAssembler) { this.personAssembler = personAssembler; } ... public CourseResource toResource(Course course) { CourseResource resource = new CourseResource(); BeanUtils.copyProperties(course, resource); Person teacher = course.getTeacher(); if(teacher != null) resource.setTeacher( personAssembler.toShortResource(teacher)); return resource; } }

8018.5.2016.

Page 81: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Dohvaćanje predmeta - CourseResource

public class CourseResource { private Long id; private String name, description; private ShortPersonResource teacher; // set i get metode}

8118.5.2016.

Page 82: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Stvaranje predmeta

public class RepresentationUtility { ... public static <R> R badRequestIfNull(R r, String message) { if(r == null) throw new BadRequestException(message); return r; } }

@ResponseStatus(HttpStatus.BAD_REQUEST) public class BadRequestException extends RuntimeException { // različiti konstruktori}

8218.5.2016.

Page 83: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Stvaranje predmeta - CourseController

public class CourseController { ... @RequestMapping(method = RequestMethod.POST) public ResponseEntity newCourse( @RequestBody CourseResourceRequest courseResource) { Course course = courseService.save( courseAssembler.toEntity(courseResource)); return ResponseEntity.created(linkTo( methodOn(CourseController.class) .getCourse(course.getId())).toUri()) .build(); } }

8318.5.2016.

Page 84: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Stvaranje predmeta - CourseAssembler

public class CourseAssembler { ... public Course toEntity(CourseResourceRequest courseResource) { Course course = new Course(); course.setName(courseResource.getName()); course.setDescription(courseResource.getDescription()); if(courseResource.getTeacherId() != null) { Person teacher = badRequestIfNull( personService.getWithId( courseResource.getTeacherId()), "Teacher does not exist."); course.setTeacher(teacher); } return course; } }

8418.5.2016.

Page 85: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Stvaranje predmeta - CourseResourceRequest

public class CourseResourceRequest { private String name, description; private Long teacherId;

// set i get metode}

8518.5.2016.

Page 86: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Promjena predmeta - CourseController

public class CourseController { ... @RequestMapping(value = "/{courseId}", method = RequestMethod.PUT) public ResponseEntity updateCourse( @PathVariable("courseId") Long id, @RequestBody CourseResourceRequest courseResourceRequest) { try { Course course = courseAssembler.toEntity( courseResourceRequest); course.setId(id); courseService.update(course); return ResponseEntity.noContent().build(); } catch (IllegalArgumentException e) { return ResponseEntity.status(HttpStatus.NOT_MODIFIED) .eTag(e.getMessage()) .build(); } }

8618.5.2016.

Page 87: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Brisanje predmeta - CourseController

public class CourseController { ... @RequestMapping(value = "/{courseId}", method = RequestMethod.DELETE) public ResponseEntity deleteCourse( @PathVariable("courseId") Long id) { resourceNotFoundIfNull(courseService.getWithId(id)); courseService.delete(id); return ResponseEntity.noContent().build(); }}

8718.5.2016.

Page 88: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Upis predmeta - CourseService i impl.

public interface CourseService { ... public void enrollPersonToCourse(Long courseId, Long studentId);}

public class CourseServiceJpaImpl implements CourseService { @Override @Transactional public void enrollPersonToCourse(Long courseId, Long studentId) { Course course = repo.findOne(courseId); if(course == null) throw new IllegalArgumentException("Course does not exist."); Person student = personRepo.findOne(studentId); if(student == null) throw new IllegalArgumentException("Student can not be null."); if(student.equals(course.getTeacher())) throw new IllegalArgumentException("Student can not be teacher in the same course."); if(course.getStudents().contains(student)) throw new IllegalArgumentException("Student already enrolled."); course.getStudents().add(student); } }

8818.5.2016.

Page 89: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Upis predmeta - CourseController

public class CourseController {... @RequestMapping(value = "/{cid}/enrollPerson/{sid}", method = RequestMethod.POST) public ResponseEntity enrollPersonInCourse( @PathVariable("cid") Long courseId, @PathVariable("sid") Long studentId) { resourceNotFoundIfNull(courseId, "Course ID can not be null."); resourceNotFoundIfNull(studentId, "Student ID can not be null."); try { courseService.enrollPersonToCourse(courseId, studentId); } catch (IllegalArgumentException e) { badRequestIfNull(null, e.getMessage()); } return ResponseEntity.noContent().build(); }

8918.5.2016.

Page 90: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Ispis predmeta

public class CourseServiceJpaImpl implements CourseService { @Override @Transactional public void enrollPersonToCourse(Long courseId, Long studentId) { Course course = repo.findOne(courseId); if(course == null) throw new IllegalArgumentException("Course does not exist."); Person student = personRepo.findOne(studentId); if(student == null) throw new IllegalArgumentException( "Student can not be null."); if(student.equals(course.getTeacher())) throw new IllegalArgumentException( "Student can not be teacher in the same course."); if(course.getStudents().contains(student)) throw new IllegalArgumentException( "Student already enrolled."); course.getStudents().add(student); }}

9018.5.2016.

Page 91: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Lista predmeta koje osoba predanje

public class PresonController {... @RequestMapping(value = "/{personId}/teachingCourses", method = RequestMethod.GET) public List<ShortCourseResource> getTeachingCourses( @PathVariable("personId") Long personId) { Person person = resourceNotFoundIfNull( service.getWithId(personId)); return courseAssembler.toListResource( person.getTeachingCourses()); } }

9118.5.2016.

Page 92: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Lista predmeta koje je osoba upisala

public class PresonController {... @RequestMapping(value = "/{personId}/enrolledCourses", method = RequestMethod.GET) public List<ShortCourseResource> getEnrolledCourses( @PathVariable("personId") Long personId) { Person person = resourceNotFoundIfNull( service.getWithId(personId)); return courseAssembler.toListResource( person.getEnrolledCourses()); } }

9218.5.2016.

Page 93: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Lista osoba koje su upisane u predmet

public class CourseController {... @RequestMapping(value = "/{cid}/students", method = RequestMethod.GET) public List<ShortPersonResource> getStudents( @PathVariable("cid") Long courseId) { Course course = resourceNotFoundIfNull( courseService.getWithId(courseId)); return personAssembler.toListResource( course.getStudents()); } }

9318.5.2016.

Page 94: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA 94

Sigurnost web-aplikacija

18.5.2016.

Page 95: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sigurnost web-aplikacija

♦ autentifikacija ! tko pokušava koristiti neki resurs? ! kako identificirati korisnika?

" pomoću korisničkog imena i lozinke se utvrđuje identitet korisnika " certifikati " ...

♦ autorizacija ! ima li korisnik/identitet pravo pristupa resursu? ! svakom identitetu su dodijeljena određena prava kroz uloge ! teba se izvoditi u slojevima (npr. URL, pozivi metoda, ...)

♦ zaštićena komunikacija ! korištenje protokola HTTPS (portovi: 443, 8443)

9518.5.2016.

Page 96: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sigurnost u Spring bootu

♦ koristi se Spring Security ♦ uključiti knjižnice:

! u build.gradle dodati dependencies {...

compile("org.springframework.boot:spring-boot-starter-security")...}

♦ dodati novi entitet Account koji će imati podatke o korisniku ♦ napraviti repozitorij AccountRepository ♦ definirati konfiguraciju sigurnosti

(WebSecurityConfigurerAdapter) ♦ dodati u sučelje usluga (npr. PersonService) preduvjete za

pojedinu metodu9618.5.2016.

Page 97: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

HTTP Basic Authentication

9718.5.2016.

klijent poslužiteljGET /

401 UnauthorizedWWW-Authenticate: Basic realm="Realm"

alt[krivo]

[uspješno]

GET /Authorization: Basic dXNlcjp1

401 UnauthorizedWWW-Authenticate: Basic realm="Realm"

GET /Authorization: Basic dXNlcjp1c2Vy

200 OK

Page 98: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Entitet koji sadrži podatke o korisniku (Account)

@Entitypublic class Account {

@Id @GeneratedValueprivate Long id;

private String username;private String password;

public Account() { }

public Account(String username, String password) {super();this.username = username;this.password = password;

}

// metode get/set}

9818.5.2016.

Page 99: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Repozitorij AccountRepository

@RepositoryRestResource(exported = false)public interface AccountRepository extends CrudRepository<Account, Long>{

public Account findByUsername(String username);}

9918.5.2016.

Page 100: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Konf. sigurnosti SecurityConfigurerAdapter(1)

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

@AutowiredAccountRepository repo;

@Overrideprotected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.anyRequest().fullyAuthenticated().and().httpBasic().and().csrf().disable();

}

...

10018.5.2016.

Page 101: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Konf. sigurnosti SecurityConfigurerAdapter(2)

@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService( new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return getUserDetails(username); } });}

10118.5.2016.

Page 102: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Konf. sigurnosti SecurityConfigurerAdapter(3)

private UserDetails getUserDetails(String username) { Account account = repo.findByUsername(username); if(account != null) { String role; if(account.getUsername().equals("admin")) role = "ROLE_ADMIN"; else role = "ROLE_USER";

return new User(account.getUsername(), account.getPassword(), true, // enabled true, // accountNonExpired true, // credentialsNonExpired true, // accountNonLocked AuthorityUtils.createAuthorityList(role, "ROLE_USER")); } else { throw new UsernameNotFoundException(...); }}

10218.5.2016.

Page 103: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Sigurnostni preduvjeti za metode

public interface PersonService {@PreAuthorize("hasAuthority('ROLE_USER')")public Iterable<Person> findAll();

@PreAuthorize("hasAuthority('ROLE_USER')")public Person getWithId(Long id);

@PreAuthorize("hasAuthority('ROLE_ADMIN')")public Person save(Person convertToEntity);

@PreAuthorize("hasAuthority('ROLE_ADMIN')")public void update(Person person);

@PreAuthorize("hasAuthority('ROLE_ADMIN')") public void delete(Long id);}

10318.5.2016.

Page 104: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Inicijalizacija početnih korisnika

@SpringBootApplicationpublic class LecturesApplication implements CommandLineRunner {...

@Autowiredprivate AccountRepository arepo;

...@Override@Transactionalpublic void run(String... args) throws Exception {

arepo.save(new Account("admin", "admin"));arepo.save(new Account("user", "user"));

...}

}

10418.5.2016.

Page 105: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Podešavanje HTTPS-a (1)

♦ dobavljanje certifikata ! kod nekog CA zahtjevati certifikat (to se plaća) ! napraviti certifikat koji je samopotpisan (datoteka lectures.jks)

10518.5.2016.

keytool-genkey-aliastomcat-storetypePKCS12-keyalgRSA-keysize2048-keystorelectures.jks-validity3650Enterkeystorepassword:Re-enternewpassword:Whatisyourfirstandlastname?[Unknown]:localhostWhatisthenameofyourorganizationalunit?[Unknown]:ZTELWhatisthenameofyourorganization?[Unknown]:FERWhatisthenameofyourCityorLocality?[Unknown]:ZagrebWhatisthenameofyourStateorProvince?[Unknown]:GradZagrebWhatisthetwo-lettercountrycodeforthisunit?[Unknown]:HRIsCN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknowncorrect?[no]:yes

Page 106: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Podešavanje HTTPS-a (2)

♦ kofiguriranje Spring boota da koristi HTTPS ! u application.properties dodati:

server.port=8443 server.ssl.key-store=lectures.jks server.ssl.key-store-password=jakotajno server.ssl.keyStoreType=PKCS12 server.ssl.keyAlias=tomcat

♦ otvoriti https://localhost:8443/rest ! Chrome javlja: "Your connection is not private" jer je certifikat

samopotpisan " keytool -export -alias tomcat -file lectures.crt -keystore lectures.jks -storetype PKCS12

" import certifikata u keystore na računalu ili u preglednik i staviti ga da je trusted

10618.5.2016.

Page 107: 9. predavanje [1,26 MiB]

Zavod za telekomunikacije

RUAZOSA

Literatura o sigurnosti u Springu

♦ Spring Security Reference ♦ Securing a Web Application ♦ REST Security with JWT using Java and Spring Security ♦ RESTful authentication using Spring Security on Spring Boot,

and jQuery as a web client ♦ Securing REST APIs With Spring Boot ♦ Stateless Spring Security Part 2: Stateless Authentication ♦ Implementing Token-Based Authentication in a Microservices

Architecture ♦ Spring Security for a REST API

10718.5.2016.