jwt token authorization
This commit is contained in:
parent
b8e8ce035c
commit
858421b9d8
5 changed files with 91 additions and 3 deletions
|
@ -4,28 +4,42 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
"weather-data/config"
|
"weather-data/config"
|
||||||
"weather-data/storage"
|
"weather-data/storage"
|
||||||
"weather-data/weathersource"
|
"weather-data/weathersource"
|
||||||
|
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bearerTokenRegexPattern = "^(?i:Bearer\\s+)([A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+\\/=]*)$"
|
||||||
|
|
||||||
|
var bearerTokenRegex *regexp.Regexp = regexp.MustCompile(bearerTokenRegexPattern)
|
||||||
|
|
||||||
|
type customClaims struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
jwt.StandardClaims
|
||||||
|
}
|
||||||
|
|
||||||
type weatherRestApi struct {
|
type weatherRestApi struct {
|
||||||
connection string
|
connection string
|
||||||
|
config config.RestConfig
|
||||||
weaterStorage storage.WeatherStorage
|
weaterStorage storage.WeatherStorage
|
||||||
weatherSource weathersource.WeatherSourceBase
|
weatherSource weathersource.WeatherSourceBase
|
||||||
sensorRegistry storage.SensorRegistry
|
sensorRegistry storage.SensorRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetupAPI sets the REST-API up
|
//SetupAPI sets the REST-API up
|
||||||
func NewRestAPI(connection string, weatherStorage storage.WeatherStorage, sensorRegistry storage.SensorRegistry) *weatherRestApi {
|
func NewRestAPI(connection string, weatherStorage storage.WeatherStorage, sensorRegistry storage.SensorRegistry, config config.RestConfig) *weatherRestApi {
|
||||||
api := new(weatherRestApi)
|
api := new(weatherRestApi)
|
||||||
api.connection = connection
|
api.connection = connection
|
||||||
api.weaterStorage = weatherStorage
|
api.weaterStorage = weatherStorage
|
||||||
api.sensorRegistry = sensorRegistry
|
api.sensorRegistry = sensorRegistry
|
||||||
|
api.config = config
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +47,7 @@ func NewRestAPI(connection string, weatherStorage storage.WeatherStorage, sensor
|
||||||
func (api *weatherRestApi) Start() error {
|
func (api *weatherRestApi) Start() error {
|
||||||
router := api.handleRequests()
|
router := api.handleRequests()
|
||||||
|
|
||||||
originsOk := handlers.AllowedOrigins([]string{config.RestConfiguration.AccessControlAllowOriginHeader})
|
originsOk := handlers.AllowedOrigins([]string{api.config.AccessControlAllowOriginHeader})
|
||||||
|
|
||||||
return http.ListenAndServe(api.connection, handlers.CORS(originsOk)(router))
|
return http.ListenAndServe(api.connection, handlers.CORS(originsOk)(router))
|
||||||
}
|
}
|
||||||
|
@ -48,10 +62,13 @@ func (api *weatherRestApi) handleRequests() *mux.Router {
|
||||||
|
|
||||||
router.HandleFunc("/", api.homePageHandler)
|
router.HandleFunc("/", api.homePageHandler)
|
||||||
|
|
||||||
|
//random weather data
|
||||||
router.HandleFunc("/{_dummy:(?i)random}", api.randomWeatherHandler).Methods("GET")
|
router.HandleFunc("/{_dummy:(?i)random}", api.randomWeatherHandler).Methods("GET")
|
||||||
router.HandleFunc("/{_dummy:(?i)randomlist}", api.randomWeatherListHandler).Methods("GET")
|
router.HandleFunc("/{_dummy:(?i)randomlist}", api.randomWeatherListHandler).Methods("GET")
|
||||||
|
|
||||||
|
//sensor specific stuff
|
||||||
sensorRouter := router.PathPrefix("/{_dummy:(?i)sensor}").Subrouter()
|
sensorRouter := router.PathPrefix("/{_dummy:(?i)sensor}").Subrouter()
|
||||||
|
sensorRouter.Use(api.IsAuthorized)
|
||||||
|
|
||||||
sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.getWeatherDataHandler).Methods("GET")
|
sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.getWeatherDataHandler).Methods("GET")
|
||||||
sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.addWeatherDataHandler).Methods("POST")
|
sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.addWeatherDataHandler).Methods("POST")
|
||||||
|
@ -61,7 +78,13 @@ func (api *weatherRestApi) handleRequests() *mux.Router {
|
||||||
sensorRouter.HandleFunc("/{id}", api.updateWeatherSensorHandler).Methods("PUT")
|
sensorRouter.HandleFunc("/{id}", api.updateWeatherSensorHandler).Methods("PUT")
|
||||||
sensorRouter.HandleFunc("/{id}", api.deleteWeatherSensorHandler).Methods("DELETE")
|
sensorRouter.HandleFunc("/{id}", api.deleteWeatherSensorHandler).Methods("DELETE")
|
||||||
|
|
||||||
|
//registration
|
||||||
router.HandleFunc("/{_dummy:(?i)register/sensor}/{name}", api.registerWeatherSensorHandler).Methods("POST")
|
router.HandleFunc("/{_dummy:(?i)register/sensor}/{name}", api.registerWeatherSensorHandler).Methods("POST")
|
||||||
|
|
||||||
|
//token generation
|
||||||
|
if api.config.AllowTokenGeneration {
|
||||||
|
router.HandleFunc("/{_dummy:(?i)generateToken}", api.generateToken).Methods("GET")
|
||||||
|
}
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +106,28 @@ func (api *weatherRestApi) randomWeatherListHandler(w http.ResponseWriter, r *ht
|
||||||
json.NewEncoder(w).Encode(storage.ToMap(datapoints))
|
json.NewEncoder(w).Encode(storage.ToMap(datapoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *weatherRestApi) generateToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := customClaims{
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
signedToken, err := token.SignedString([]byte(config.RestConfiguration.JwtTokenSecret))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]string{
|
||||||
|
"Autohrization": signedToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("content-type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
func (api *weatherRestApi) getWeatherDataHandler(w http.ResponseWriter, r *http.Request) {
|
func (api *weatherRestApi) getWeatherDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
@ -247,6 +292,41 @@ func (api *weatherRestApi) homePageHandler(w http.ResponseWriter, r *http.Reques
|
||||||
fmt.Fprintf(w, "Welcome to the Weather API!")
|
fmt.Fprintf(w, "Welcome to the Weather API!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *weatherRestApi) IsAuthorized(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !api.config.UseTokenAuthorization {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizationHeader := r.Header["Authorization"]
|
||||||
|
if authorizationHeader == nil {
|
||||||
|
http.Error(w, "no bearer token", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtFromHeader := bearerTokenRegex.FindStringSubmatch(authorizationHeader[0])[1]
|
||||||
|
|
||||||
|
token, err := jwt.ParseWithClaims(
|
||||||
|
jwtFromHeader,
|
||||||
|
&customClaims{},
|
||||||
|
func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(api.config.JwtTokenSecret), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Valid {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data
|
//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data
|
||||||
func (api *weatherRestApi) AddNewWeatherDataCallback(callback weathersource.NewWeatherDataCallbackFunc) {
|
func (api *weatherRestApi) AddNewWeatherDataCallback(callback weathersource.NewWeatherDataCallbackFunc) {
|
||||||
api.weatherSource.AddNewWeatherDataCallback(callback)
|
api.weatherSource.AddNewWeatherDataCallback(callback)
|
||||||
|
|
|
@ -32,6 +32,9 @@ type MqttConfig struct {
|
||||||
|
|
||||||
type RestConfig struct {
|
type RestConfig struct {
|
||||||
AccessControlAllowOriginHeader string
|
AccessControlAllowOriginHeader string
|
||||||
|
UseTokenAuthorization bool
|
||||||
|
AllowTokenGeneration bool
|
||||||
|
JwtTokenSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
var MongoConfiguration = MongoConfig{
|
var MongoConfiguration = MongoConfig{
|
||||||
|
@ -60,6 +63,9 @@ var MqttConfiguration = MqttConfig{
|
||||||
|
|
||||||
var RestConfiguration = RestConfig{
|
var RestConfiguration = RestConfig{
|
||||||
AccessControlAllowOriginHeader: getEnv("ACCESS_CONTROL_ALLOW_ORIGIN_HEADER", "*"),
|
AccessControlAllowOriginHeader: getEnv("ACCESS_CONTROL_ALLOW_ORIGIN_HEADER", "*"),
|
||||||
|
UseTokenAuthorization: getEnvBool("USE_TOKEN_AUTHORIZATION", false),
|
||||||
|
AllowTokenGeneration: getEnvBool("ALLOW_TOKEN_GENERATION", false),
|
||||||
|
JwtTokenSecret: getEnv("JWT_TOKEN_SECRET", "jwt-token-secret"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var AllowUnregisteredSensors = getEnvBool("ALLOW_UNREGISTERED_SENSORS", false)
|
var AllowUnregisteredSensors = getEnvBool("ALLOW_UNREGISTERED_SENSORS", false)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module weather-data
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.2
|
github.com/eclipse/paho.mqtt.golang v1.3.2
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gorilla/handlers v1.5.1 // indirect
|
github.com/gorilla/handlers v1.5.1 // indirect
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -7,6 +7,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deepmap/oapi-codegen v1.3.13 h1:9HKGCsdJqE4dnrQ8VerFS0/1ZOJPmAhN+g8xgp8y3K4=
|
github.com/deepmap/oapi-codegen v1.3.13 h1:9HKGCsdJqE4dnrQ8VerFS0/1ZOJPmAhN+g8xgp8y3K4=
|
||||||
github.com/deepmap/oapi-codegen v1.3.13/go.mod h1:WAmG5dWY8/PYHt4vKxlt90NsbHMAOCiteYKZMiIRfOo=
|
github.com/deepmap/oapi-codegen v1.3.13/go.mod h1:WAmG5dWY8/PYHt4vKxlt90NsbHMAOCiteYKZMiIRfOo=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.2 h1:ICzfxSyrR8bOsh9l8JBBOwO1tc2C26oEyody0ml0L6E=
|
github.com/eclipse/paho.mqtt.golang v1.3.2 h1:ICzfxSyrR8bOsh9l8JBBOwO1tc2C26oEyody0ml0L6E=
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.2/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
github.com/eclipse/paho.mqtt.golang v1.3.2/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
||||||
|
|
2
main.go
2
main.go
|
@ -42,7 +42,7 @@ func main() {
|
||||||
weatherSource.AddNewWeatherDataCallback(handleNewWeatherData)
|
weatherSource.AddNewWeatherDataCallback(handleNewWeatherData)
|
||||||
|
|
||||||
//setup a API -> REST
|
//setup a API -> REST
|
||||||
weatherAPI = api.NewRestAPI(":10000", weatherStorage, sensorRegistry)
|
weatherAPI = api.NewRestAPI(":10000", weatherStorage, sensorRegistry, config.RestConfiguration)
|
||||||
defer weatherAPI.Close()
|
defer weatherAPI.Close()
|
||||||
weatherAPI.AddNewWeatherDataCallback(handleNewWeatherData)
|
weatherAPI.AddNewWeatherDataCallback(handleNewWeatherData)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue