Upload
phungtram
View
246
Download
7
Embed Size (px)
Citation preview
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
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.