Webentwicklung - Webarchitekturen (Fortsetzung)Hochschule für Technik und Wirtschaft Berlin, Beuth Hochschule für Technik Berlin, Dipl.-Inform. Thomas Ziemer |
|
|
Dipl.-Inform. Thomas Ziemer
%messages.pagePresentation.showMaximized%Nützliche Installations- und Konfigurationshinweise können im GitLab-Repository in der README.md des Projekts von swXercise gefunden werden.
Die Referenzinstallation mit dem gesamten angegebenen Tech Stack (inkl. swXercise) kann ebenfalls heruntergeladen und als virtuelle Maschine in Betrieb genommen werden.
@ApplicationPath(RestApplication.webContextPath)
public class RestApplication extends Application {
static final String webContextPath = "/rest";
}
Die Klasse RestApplication
definiert den Startpunkt (Pfad) aller REST-Methoden.
@Inject
private UserService userService;
…
@POST
@Path("v1/user/login")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed(RightState.Constants.NOT_LOGGED_IN)
public RestResponse loginUser(UserDto dto) {
if (userService.loginUser(dto)) {
return new RestResponse();
}
return new RestResponse(ResponseState.FAILED);
}
@Inject
wird eine Objekt-Referenz injiziert@POST
entscheidet mit über den aufgerufenen REST-Service@PATH
benennt den betreffenden REST-Service@MediaType.JSON_APPLICATION
wird JSON-Marshalling durchgeführt@RolesAllowed()
und @DenyAll
werden Zugriffsrechte geprüft
public class RestResponse {
private ResponseState responseState;
private String message;
public RestResponse() {
this(ResponseState.SUCCESS);
}
public RestResponse(final ResponseState responseState) {
this.responseState = responseState;
}
public int getResponseCode() {
return responseState.getResponseCode();
}
}
@Inject
private UserDao dao; // ein Data Access Object
@Inject
private SessionContext sessionContext;
…
public boolean loginUser(UserDto dto) {
final User user = dao.findByUsername(dto.getUsername());
return user != null
&& user.getProfile().isValidPassword(dto.getPassword())
&& sessionContext.login(user);
}
public User findByUsername(final String username) {
User user = null;
try {
// ermittelt den ersten Datensatz mit dem gesuchten
// Benutzernamen, auch wenn er sich nicht im
// Persistence Context befindet
user = (User) entityManager
.createNamedQuery("User.findByUsername")
.setParameter("username", username)
.getSingleResult();
} catch (Exception e) {
/* nix */
}
return user;
}
entityManager
kommt aus dem db.dao.GenericDao
db.dao.GenericDao
bietet generische Zugriffsmethoden auf die Datenhaltung:
public <T extends BaseEntity> Long save(final T entity)
public <T extends BaseEntity> T saveOrUpdate(final T entity)
public <T extends BaseEntity> T remove(Class<T> entityType, Long primaryKey)
public <T extends BaseEntity> T findById(Class<T> entityType, Long primaryKey)
public <T extends BaseEntity> Collection<T> findAll(Class<T> entityType)
@Entity
@NamedQueries({
@NamedQuery(name = "User.findById",
query = "SELECT u FROM User u WHERE u.id = :id"),
@NamedQuery(name = "User.findByUsername",
query = "SELECT u FROM User u
WHERE lower(u.profile.username) =
lower(:username)")})
public class User extends BaseEntity {
…
user = (User) entityManager
.createNamedQuery("User.findByUsername")
.setParameter("username", username)
.getSingleResult();
public boolean isValidPassword(final String password) {
final String passwordHash = cryptString(password);
return this.getPasswordHash().equals(passwordHash);
}
private String cryptString(String string) {
String hashedString = "";
// vor dem Verschlüsseln den Salt an das Kennwort anhängen
string += this.getSalt();
…
byte[] bytes = md.digest(string.getBytes("UTF-8"));
…
hashedString = byteToBase64(bytes);
return hashedString;
}
In REST-Zugriffen werden diese Methoden häufig gemäß CRUD-Konzept verwendet:
Bedeutung | |
---|---|
GET | Anfragen (read) einer Host-Ressource (lesend ohne Seiteneffekte) |
POST | Aktualisieren (update) einer Host-Ressource (oder anlegen oder anfragen) |
PUT | Anlegen (create) einer Host-Ressource (idempotent; mehrfache Ausführung ändert den Serverzustand nicht weiter) |
DELETE | Löschen (delete) einer Host-Ressource (idempotent) |
Auch der Pfad einer REST-Methode folgt üblichen Konventionen:
/<project>/rest/api/<version>/<context>/<service>[/{parameters}]
Beispielsweise
GET /swXercise/rest/api/v1/users
GET /swXercise/rest/api/v1/user/{userid}
POST /swXercise/rest/api/v1/user/login
PUT /swXercise/rest/api/v2/user/register
DELETE /swXercise/rest/api/v1/user/{userid}
GET /swXercise/rest/help/v1/user/login
…
@ServerEndpoint(WebSocketController.serverEndpointPath)
public class WebSocketController {
static final String serverEndpointPath = "/ws/api/v1/anEndpoint";
@OnOpen
public void onOpen(Session session) { … }
@OnMessage
public void onMessage(String message, Session session) {
// einfach die empfangene Nachricht zurückschicken
session.getBasicRemote().sendText(message);
}
@OnClose
public void onClose(CloseReason reason, Session session) { … }
}
}
@ServerEndpoint(value = WebSocketController.serverEndpointPath,
encoders = { WebSocketJson.MessageEncoder.class },
decoders = { WebSocketJson.MessageDecoder.class } )
public class WebSocketController {
static final String serverEndpointPath =
"/ws/api/v1/anEndpoint/{aParameter}";
@OnMessage
public void onMessage(WebSocketJson json, Session session,
@PathParam("aParameter") String param)
throws IOException, EncodeException {
session.getBasicRemote().sendObject(json);
}
…
public static class MessageEncoder
implements Encoder.Text<WebSocketJson> {
@Override
public String encode(WebSocketJson message)
throws EncodeException {
return Json.createObjectBuilder()
.add("message", message.getMessage())
.build()
.toString();
}
…
}
public static class MessageDecoder
implements Decoder.Text<WebSocketJson> {
private JsonReaderFactory factory
= Json.createReaderFactory(Collections.emptyMap());
@Override
public WebSocketJson decode(String str) throws DecodeException {
WebSocketJson message = new WebSocketJson();
JsonReader reader = factory.createReader(new StringReader(str));
JsonObject json = reader.readObject();
message.setMessage(json.getString("message"));
return message;
}
Cross-Origin Resource Sharing (CORS) ist ein Mechanismus, der Browsers und anderen Webklienten Cross-Origin-Requests ermöglicht, also Zugriffe über Domain-Grenzen hinweg.
Zugriffe dieser Art sind normalerweise durch die Same-Origin-Policy (SOP) untersagt. CORS ist ein Kompromiss zugunsten größerer Flexibilität im Internet unter Berücksichtigung möglichst hoher Sicherheitsmaßnahmen.
Damit die Anfrage einer Webapplikation, z.B. http://foo.example/application.html, an einen Server einer abweichenden Domain, z.B. http://bar.example/rest/method, erfolgreich durchgeführt werden kann, muss der Server bei seiner Antwort den Zugriff des Browsers durch einen entsprechenden HTTP-Header erlauben.
CORS-Ressourcen, die z.B. ausschließlich über dieGET
-Methode erreichbar sein sollen, würden mittels Access-Control-Allow-Methods: GET
erlaubt werden. Der Origin
-Header kann (beispielsweise mittels cUrl) problemlos gespooft werden. CORS bietet also keine wirkliche Sicherheit! Man darf sich nicht auf sie verlassen.
Schützenswerte Kommunikation muss zusätzlich mittels Access-Tokens o.ä. abgesichert werden.