9. predavanje [1,26 mib]

Post on 28-Jan-2017

247 Views

Category:

Documents

7 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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/

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.

Zavod za telekomunikacije

RUAZOSA

Sadržaj predavanja

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

318.5.2016.

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.

Zavod za telekomunikacije

RUAZOSA

Arhitektura Spring Frameworka - dokumentacija

518.5.2016.

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.

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.

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.

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.

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.

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.

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

Zavod za telekomunikacije

RUAZOSA 13

Oblikovni obrazac MVC Model View Controller

18.5.2016.

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.

Zavod za telekomunikacije

RUAZOSA 15

JPA Java Persistance API

18.5.2016.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Klasa PersonServiceJpaImpl (3)

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

2618.5.2016.

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.

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.

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

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

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

♦ sadržaj nije bitan

3418.5.2016.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

3718.5.2016.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

3818.5.2016.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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..*

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.

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.

Zavod za telekomunikacije

RUAZOSA

Baza podataka

5418.5.2016.

Zavod za telekomunikacije

RUAZOSA

Sučelje CourseRepository

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

5518.5.2016.

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.

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.

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

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

♦ sadržaj nije bitan

6218.5.2016.

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.

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.

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

6818.5.2016.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

6918.5.2016.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

7018.5.2016.

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.

Zavod za telekomunikacije

RUAZOSA

Primjer upita i odgovora

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

7218.5.2016.

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.

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.

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

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.

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.

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.

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.

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.

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.

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA

Stvaranje predmeta - CourseResourceRequest

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

// set i get metode}

8518.5.2016.

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.

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.

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.

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.

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.

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.

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.

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.

Zavod za telekomunikacije

RUAZOSA 94

Sigurnost web-aplikacija

18.5.2016.

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.

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.

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

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.

Zavod za telekomunikacije

RUAZOSA

Repozitorij AccountRepository

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

public Account findByUsername(String username);}

9918.5.2016.

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.

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.

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.

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.

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.

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

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.

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.

top related