Folge mir auf Twitter.

Webentwicklung - Webarchitekturen (Fortsetzung)

Hochschule für Technik und Wirtschaft Berlin, Beuth Hochschule für Technik Berlin

Dipl.-Inform. Thomas Ziemer

Webentwicklung - Webarchitekturen (Fortsetzung)

Dipl.-Inform. Thomas Ziemer

Webentwicklung - Webarchitekturen (Fortsetzung)

Technology Stack

Tech Stack

Tech Stack (Fortsetzung)

Installation und Konfiguration

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.

Webentwicklung - Webarchitekturen (Fortsetzung)

Backend

Webarchitektur (Backend)

Klassenmodell (Ausschnitt)

Webentwicklung - Webarchitekturen (Fortsetzung)

Geschäftsprozess
"Benutzer anmelden"

Klasse ui.utils.RestApplication

@ApplicationPath(RestApplication.webContextPath)
public class RestApplication extends Application {

   static final String webContextPath = "/rest";

}

Die Klasse RestApplication definiert den Startpunkt (Pfad) aller REST-Methoden.

Methode loginUser() in ui.UserViewController

@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);
}

Bemerkungen

Klasse ui.utils.RestResponse

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();
   }
}

Meth. loginUser() in lg.user.service.UserService

@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);
}

Meth. findByUsername() in db.dao.User.UserDao

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;
}

Bemerkungen

Klasse lg.model.User

@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();

Webentwicklung - Webarchitekturen (Fortsetzung)

Konventionen für Kennwörter

Meth. isValidPassword() in lg.model.user.Profile

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;
}

Bemerkungen

Webentwicklung - Webarchitekturen (Fortsetzung)

Konventionen für REST-Methoden

Konventionen für REST-Methoden

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) 

Konventionen für REST-Methoden

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
…

Webentwicklung - Webarchitekturen (Fortsetzung)

WebSockets

WebSockets

Klasse ui.ws.WebSocketController

@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) { … }
   }
}

Klasse ui.ws.WebSocketController (mit JSON)

@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);
   }
   …

Klasse ui.ws.WebSocketJson (MessageEncoder)

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();
   }

   …

}

Klasse ui.ws.WebSocketJson (MessageDecoder)

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;
   }

Bemerkungen

Webentwicklung - Webarchitekturen (Fortsetzung)

Cross-Origin Resource Sharing

Cross-Origin Resource Sharing (CORS)

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.

Same-Origin-Policy (SOP)

Cross-Origin Resource Sharing (CORS)

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 die GET-Methode erreichbar sein sollen, würden mittels Access-Control-Allow-Methods: GET erlaubt werden.

Cross-Origin Resource Sharing (CORS)

The Origin Request HTTP-Header indicates where a fetch originates from. It doesn't include any path information, but only the server name. It is sent with CORS requests, as well as with POST requests. It is similar to the Referer header, but, unlike this header, it doesn't disclose the whole path.

Origin: <scheme> "://" <host> [ ":" <port> ]

Aus dem entsprechenden RFC-Dokument

Cross-Origin Resource Sharing (CORS)

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.

Same-Origin-Policy (SOP)

Puh, fertig!