Pokazywanie postów oznaczonych etykietą json. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą json. Pokaż wszystkie posty

wtorek, 29 września 2015

[Security] JsonWebTokens

Celem jest stworzenie struktury reprezentującej informacje na temat tożsamości, wydającego itd. Security tokens są chronionymi strukturami danych (podpisane, zawierają czas wygasanie). Składają się z trzech sekcji: nagłówka, payloadu oraz podpisu weryfikującego.

Proces wydawania tokenu może zastępować klasyczny proces autentykacji użytkownika do aplikacji (zamiast cookie otrzymuje on token będący stringiem, który później dołącza do swoich żądan, np. jako nagłówek HTTP).

JWT wykorzystuje różne mechanizmy kryptograficzne. Najpopularniejszym z nich jest HMACSHA256, który "wylicza" skrót z contentu przy użyciu klucza symetrycznego. W innych wariantach można spotkać się z RSA, a nawet z kryptografią krzywych eliptycznych. W przypadku aplikacji webowych serwer wydaje tokeny i ten sam serwer je później weryfikuje, więc tajny klucz znajduje się w jednym miejscu. Aplikacja JS może takie tokeny dołączać np. jako nagłówek HTTP.

Przykładowy JWT wraz z zdekodowaną postacią (kodowanie Base64).




Serwerowo w .NET mamy do dyspozycji NuGet od Microsoftu.



Przykładowa aplikacja w C# weryfikująca integralność na podstawie współdzielonego klucza:

static void Main(string[] args)
{
    var pass = "AC917771A299CF9542513AED8885D12";
    var token = CreateJsonWebToken(pass);

    Console.WriteLine(token);

    var payload = ParseAndValidateJwtToken(token, pass);
    Console.WriteLine(payload.Last().Value);

    token = token.Replace("Z", "6");

    ParseAndValidateJwtToken(token, pass);

    Console.Read();
}

private static string CreateJsonWebToken(string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();

    var descriptor = new SecurityTokenDescriptor();
    DateTime now = DateTime.Now;

    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    descriptor.TokenIssuerName = "mySecretApp";
    descriptor.AppliesToAddress = "http://mydomain.com";
    descriptor.Lifetime = new System.IdentityModel.Protocols.WSTrust.Lifetime(now, now.AddHours(1));
    descriptor.Subject = new System.Security.Claims.ClaimsIdentity();
    descriptor.SigningCredentials = new SigningCredentials(securityKey,
        "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
        "http://www.w3.org/2001/04/xmlenc#sha256");


    JwtSecurityToken token = (JwtSecurityToken)tokenHandler.CreateToken(descriptor);
    token.Payload.Add("myKey", "myValue");            

    return tokenHandler.WriteToken(token);
}

private static JwtPayload ParseAndValidateJwtToken(string jwtToken, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    try
    {
        ValidationProcedure(jwtToken, pass);
    }            
    catch(SignatureVerificationFailedException e)
    {
        Console.WriteLine("Tampering attack detected");
    }

    JwtSecurityToken parsedJwt = tokenHandler.ReadToken(jwtToken) as JwtSecurityToken;
    return parsedJwt.Payload;
}

private static void ValidationProcedure(string token, string pass)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = System.Text.Encoding.Default.GetBytes(pass);
    var securityKey = new InMemorySymmetricSecurityKey(key);

    TokenValidationParameters validationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        RequireSignedTokens = true,
        ValidateAudience = false,
        IssuerSigningKey = securityKey
    };

    SecurityToken securityToken;
    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
}
}

piątek, 27 września 2013

[FullTextSearch] ElasticSearch: Query DSL

Zapytania do ElasticSearch wysyłamy REST-owo. Do prostego pobierania dokumentów wystarczą nam sparametryzowane zapytania typu GET. Aby jednak poznać pełnię możliwości ElasticSearch musimy poznać język zapytań DSL, w którym parametry zapytania ustawiamy poprzez załączenie z żądaniem typu POST odpowiedniego obiektu w postaci JSON. ElasticSearch udostępnia wiele rodzajów zapytań. Poniżej ciekawsze z nich.

Term Query

Tego typu zapytania można wykonywać na dwa sposoby. Przy użyciu czasownika GET

http://localhost:9200/books/book/_search?q=title:parallel&pretty=true

lub za pomocą czasownika POST

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 59

{
 "query" : {
  "term" : { "title" : "parallel" }
 }
}

W obu przypadkach otrzymujemy ten sam rezultat

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 809

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.095891505,
    "hits" : [ {
      "_index" : "books",
      "_type" : "book",
      "_id" : "1",
      "_score" : 0.095891505, "_source" : {
 "title": "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
 "author": "Michio Kaku",
 "year": 2006,
 "price": 12.06
}
    }, {
      "_index" : "books",
      "_type" : "book",
      "_id" : "3",
      "_score" : 0.095891505, "_source" : {
 "title": "Hyperspace: A Scientific Odyssey Through Parallel Universes, Time Warps, and the Tenth Dimension",
 "author": "Michio Kaku",
 "year": 1995,
 "price": 16.36
}
    } ]
  }
}

Term Query to takie zapytanie, które zwraca wyniki tylko wtedy, gdy podamy dokładnie słowo, które znajduje się w danym polu. Nie zadziałają tutaj zapytania przedrostowe typu "paral*". Wyniki za każdym razem dostępne będą w tablicy dostępnej pod hits.hits. Pełny dokument zwracany jest w polu _source. Pobieranie tak zagnieżdżonych informacji wydaje się niewygodne, a czasami także niepotrzebnie przesyłany jest cały duży dokument, dlatego możemy podać, które pola nas interesują.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 92

{
 "fields" : ["title","author"],
 "query" : {
  "term" : { "title" : "parallel" }
 }
}

W rezultacie otrzymamy:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 797

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.095891505,
    "hits" : [ {
      "_index" : "books",
      "_type" : "book",
      "_id" : "1",
      "_score" : 0.095891505,
      "fields" : {
        "title" : "Parallel Worlds: A Journey Through Creation, Higher Dimensions, and the Future of the Cosmos",
        "author" : "Michio Kaku"
      }
    }, {
      "_index" : "books",
      "_type" : "book",
      "_id" : "3",
      "_score" : 0.095891505,
      "fields" : {
        "title" : "Hyperspace: A Scientific Odyssey Through Parallel Universes, Time Warps, and the Tenth Dimension",
        "author" : "Michio Kaku"
      }
    } ]
  }
}

Odpytywać można także tylko indeks (bez podawania typu book) oraz wiele indeksów lub wiele typów. Na przykład:

http://localhost:9200/books,books5/book/_search?q=author:kaku&pretty=true

Terms Query

Podajemy kilka wartości, które nie będą analizowane (muszą to być dokładne wartości), oraz opcjonalnie ile z nich musi pasować, aby zwrócony został dany dokument.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 111

{
 "query" : {
  "terms" : { "title" : ["parallel","hyperspace","worlds"],
  "minimum_match" : 2
  }
 }
}

Match Query

W tym zapytaniu podane wyrażenie zostaje przetworzone przez analizator. A zatem tym razem możemy podawać niepełne wyrazy, na przykład:

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 65

{
 "query" : {
  "match" : { "title" :"para worlds"
  }
 }
}

Zapytanie takie zwróci wyniki. Domyślnie słowa są łączone operatorem OR, można to zmienić w następujący sposób.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 122

{
 "query" : {
  "match" : {
   "title" : {
    "query" : "parallel worlds",
    "operator" : "and"
   }
  }
 }
}

Match Phrase

Jeżeli chcemy wyszukać frazy składającej się z większej ilości słów, korzystamy z match_phrase, gdzie parametr slop definiuje, ile nieznanych słów może się mieścić pomiędzy podanymi wyrazami.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 121

{
 "query" : {
  "match_phrase" : {
   "title" : {
    "query" : "parallel worlds",
    "slop" : 1
   }
  }
 }
}

Multi Match

Efekt zapytania typu "Match" możemy uzyskać także na wielu polach dokumentu.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 131

{
 "query" : { 
  "multi_match" : {
   "query" : "univers michio penrose",
   "fields" : [ "title", "author" ]
  }
 }
}

Identifiers Query

Kiedy dokładnie znamy identyfikatory dokumentów, które chcemy pobrać, korzystamy z tego typu zapytania.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 73

{
 "query" : {
  "ids" : {
   "values" : [ "1", "4", "5" ]
  }
 }
}

Prefix Query

Wyszukiwanie po przedrostkach.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 63

{
 "query" : {
  "prefix" : {
   "title" : "uni"
  }
 }
}

Fuzzy Like This Query

Zapytanie, które dopuszcza literówki w podanej frazie.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 115

{
 "query" : {
  "fuzzy_like_this" : {
   "fields" : ["title", "author"],
   "like_text" : "penrse"
  }
 }
}

Range Query

Podajemy zakresy, przydatne dla wartości numerycznych.

POST http://localhost:9200/books/book/_search?pretty=true HTTP/1.1
User-Agent: Fiddler
content-type: application/json
Host: localhost:9200
Content-Length: 100

{
 "query" : {
  "range" : {
   "year" : {
    "from" : 1990,
    "to" : 2000
   }
  }
 }
}

wtorek, 20 sierpnia 2013

[C#|Visual Studio] ServiceStack: Wprowadzenie

ServiceStack to framework będący alternatywą dla WCF, WebAPI czy MVC. Jedną z głównych zalet jest cross - platformowość. Autorem Service Stacka nie jest Microsoft, ale community, które udostępnia go na zasadzie open source. Co więcej aplikacje pisane w ServiceStack nie muszą być hostowane na IIS, można je kompilować na Mono i uruchamiań np, na Linuxie.

Nazwa ServiceStack ma symbolizować stos technologii, na jakich budowane są aplikacje.


Kolejnymi względami przemawiającymi za tą technologią są prostota w stosunku do innych frameworków (w ServiceStack mamy do czynienia z konwencjami, które eliminują sporo zbędnych konfiguracji), oraz wydajność poszczególnych komponentów w porównaniu z alternatywnymi technologiami, głównie narzędzia ORM, kontenera IoC, serializator JSON.

W Visual Studio Service Stack instalujemy za pomocą NuGeta.

Przy instalacji do projektu załączane są dodatkowe .dll - ki, takie jak Text (serializacja JSON), Redis (api .NET do bazy NoSql), OrmLite (mapper obiektowo relacyjny).

Web Serwisy pisane w ServiceStacku bazować mają na Data Transfer Objects. Ideę DTO przedstawia poniższy rysunek

Parametrem każdego requestu jest prosty obiekt DTO mający ustawione odpowiednie pola. Jeżeli np. chcemy odpytywać jakąś kolekcję po różnych parametrach, to ustawiamy w danym momencie ten, który nas interesuje w obiekcie DTO.

Strona ServiceStack: http://www.servicestack.net

niedziela, 17 marca 2013

[NoSQL] MongoDB + Node.js: mongoose

Interesującą konfiguracją dla aplikacji webowych jest Node.js jako serwer http udostępniający RESTowe API oraz MongoDB jako baza danych. Za takim pomysłem przemawia przede wszystkim format danych: klient przysyła JSONa, Node.js automatycznie parsuje go do obiektu JavaScript na którym można wykonać pewne operacje a później zapisać w bazie jako JSON znowu.

Klientem MongoDB w Node.js jest mongoose. Można go pobrać z menadżera pakietów poleceniem

npm install mongoose

Po dodaniu pakietu do serwera Node.js przy użyciu konstrukcji require, można rozpocząć pracę z MongoDB. Pakiet mongoose do CRUDowych operacji wykorzystuje obiekty typu Schema. Definiując taki obiekt podajemy jakiego typu pola znajdować się będą w naszych dokumentach. Dzięki temu, wstawiając dokument do bazy mamy pewność, że wstawione zostaną jedynie te pola, które zdefiniowaliśmy w schemacie. Mechanizm ten jest konfigurowalny poprzez opcję strict. Jeżeli ustawimy ją na false, to do bazy trafiać będą całe dokumenty. Jeden schemat mapowany będzie na jedną kolekcję w bazie przy zachowaniu zasad języka angielskiego. Na przykład schemat Car zmapowany zostanie do kolekcji cars. Poniżej prosta konfiguracja:
var mongoose = require('mongoose');
mongoose.connect('localhost','mongoosetest');

var schema = mongoose.Schema({ 
    make: 'string',
    enginePower : 'number'
 }, {strict: false});

var Car = mongoose.model('Car', schema);

Dodawanie dokumentów do bazy przy odbieraniu wiadomości typu POST:
app.post('/store/', function(req, resp) {
    var nm = req.body.name;
    var pw = req.body.enginePower;
    var car = new Car({ make: nm, enginePower: pw, day: "17-03" });
    car.save(function(err){
        if(err){
            resp.send({
                ok: 'sth went wrong'
            });
        }
        else{
            resp.send({
                ok: 'everything is fine'
            });
        }       
    });    
});

Analogicznie pobieranie danych wykonujemy także na obiekcie modelu;
app.get('/store/:key', function(req, resp) {
    Car.find({make: req.params.key}, function(err, docs){
        if(err)
            resp.send(501);
        else
            resp.send(docs);
    })
});

Funkcja find może przekazywać bardziej skomplikowane zapytania MongoDB, te same, które dostępne są w shellu bazy (program mongo.exe).

Z kolei aby usunąć obiekt, używamy funkcji remove wołanej również na modelu. Jako pierwszy parametr podaje się warunek.
app.del('/store/:key', function(req, resp) {
    Car.remove({make: req.params.key}, function(err, docs){
        if(err)
            resp.send(500);
        else
            resp.send(200);
    });
});

czwartek, 14 marca 2013

[NoSQL] MongoDB: Wprowadzenie

Jedną z najpopularniejszych baz NoSQL jest MongoDB. Podobnie jak RavenDB opisany w poprzednich postach jest to baza dokumentowa. Z poziomu API dane są składowane jako dokumenty JSONowe, jednak fizycznie na dysku składowanie odbywa się w postaci binarnych JSONów nazywanych BSON. Każdy dokument otrzymuje od bazy własne unikatowe id.

Kilka zalet i ciekawych funkcjonalności:
  • możliwość tworzenia indeksów na dowolnych polach dokumentów
  • mechanizm automatycznej replikacji danych
  • sharding - balansowanie obciążenia na wiele komputerów
  • bogaty język zapytań
  • oficjalne connectory dla wielu popularnych języków programowania 
Aby rozpocząć pracę z MongoDB należy pobrać pliki ze strony producenta odpowiednią wersję. W folderze należy utworzyć folder, w którym przechowywane będą dane, np. mydata. Serwis mongodb uruchamiany jest z command line, po wejściu do folderu z pobranymi plikami, poleceniem:
mongod --rest --port 27017 --dbpath mydata

Konfiguracja serwera bazy odbywa się również przez command line. Należy wykorzystać polecenie mongo podając numer portu, np.
mongo  --port 27017

Z poziomu shella można wykonywać proste komendy w języku JavaScript, np. zapytania.