Просмотр исходного кода

feat: added casdoor sso support (#204)

Jray 1 год назад
Родитель
Сommit
7fe3517afe

+ 7 - 0
app.example.ini

@@ -8,6 +8,13 @@ StartCmd = login
 Database = database
 CADir =
 Demo =
+CasdoorEndpoint =
+CasdoorClientId =
+CasdoorClientSecret =
+CasdoorCertificate =
+CasdoorOrganization =
+CasdoorApplication =
+CasdoorRedirectUri =
 
 [nginx]
 AccessLogPath = /var/log/nginx/access.log

+ 9 - 0
frontend/src/api/auth.ts

@@ -13,6 +13,15 @@ const auth = {
             login(r.token)
         })
     },
+    async casdoorLogin(code: string, state: string) {
+        await http.post("/casdoor_callback", {
+            code: code,
+            state: state,
+        })
+        .then((r) => {
+            login(r.token)
+        })
+    },
     logout() {
         return http.delete('/logout').then(async () => {
             logout()

+ 36 - 0
frontend/src/views/other/Login.vue

@@ -8,6 +8,8 @@ import {Form, message} from 'ant-design-vue'
 import auth from '@/api/auth'
 import install from '@/api/install'
 import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
+import http from '@/lib/http'
+import {onMounted} from 'vue'
 
 const thisYear = new Date().getFullYear()
 
@@ -70,6 +72,37 @@ watch(() => gettext.current, () => {
     clearValidate()
 })
 
+const has_casdoor = ref(false)
+const casdoor_uri = ref('')
+
+http.get("/casdoor_uri")
+    .then((response) => {
+        if (response?.uri) {
+            has_casdoor.value = true
+            casdoor_uri.value = response.uri
+        }
+    })
+    .catch((e) => {
+        message.error($gettext(e.message ?? 'Server error'))
+    });
+
+const loginWithCasdoor = () => {
+    window.location.href = casdoor_uri.value
+}
+
+if (route.query?.code != undefined && route.query?.state != undefined) {
+    loading.value = true
+    auth.casdoorLogin(route.query.code.toString(), route.query.state.toString()).then(async () => {
+        message.success($gettext('Login successful'), 1)
+        const next = (route.query?.next || '').toString() || '/'
+        await router.push(next)
+    }).catch(e => {
+        message.error($gettext(e.message ?? 'Server error'))
+    })
+    loading.value = false
+}
+
+
 </script>
 
 <template>
@@ -105,6 +138,9 @@ watch(() => gettext.current, () => {
                     </a-button>
                 </a-form-item>
             </a-form>
+            <a-button @click="loginWithCasdoor" :block="true" html-type="submit" :loading="loading" v-if="has_casdoor">
+                {{ $gettext('SSO Login') }}
+            </a-button>
             <div class="footer">
                 <p>Copyright © 2020 - {{ thisYear }} Nginx UI</p>
                 Language

+ 2 - 1
go.mod

@@ -7,6 +7,7 @@ toolchain go1.21.0
 require (
 	github.com/0xJacky/pofile v0.2.1
 	github.com/BurntSushi/toml v1.3.2
+	github.com/casdoor/casdoor-go-sdk v0.32.1
 	github.com/creack/pty v1.1.20
 	github.com/dustin/go-humanize v1.0.1
 	github.com/fatih/color v1.15.0
@@ -236,7 +237,7 @@ require (
 	golang.org/x/arch v0.5.0 // indirect
 	golang.org/x/mod v0.12.0 // indirect
 	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/oauth2 v0.12.0 // indirect
+	golang.org/x/oauth2 v0.13.0 // indirect
 	golang.org/x/sys v0.13.0 // indirect
 	golang.org/x/text v0.13.0 // indirect
 	golang.org/x/time v0.3.0 // indirect

+ 4 - 2
go.sum

@@ -149,6 +149,8 @@ github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKE
 github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
 github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
 github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
+github.com/casdoor/casdoor-go-sdk v0.32.1 h1:SMMbicerANhceSiYdzFN2woZyf63aPemvZVT7fsJu+A=
+github.com/casdoor/casdoor-go-sdk v0.32.1/go.mod h1:hVSgmSdwTCsBEJNt9r2K5aLVsoeMc37/N4Zzescy5SA=
 github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
 github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -1013,8 +1015,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
+golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

+ 62 - 0
server/api/auth.go

@@ -1,7 +1,10 @@
 package api
 
 import (
+	"fmt"
 	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
 	"github.com/gin-gonic/gin"
 	"golang.org/x/crypto/bcrypt"
 	"net/http"
@@ -55,3 +58,62 @@ func Logout(c *gin.Context) {
 	}
 	c.JSON(http.StatusNoContent, nil)
 }
+
+type CasdoorLoginUser struct {
+	Code  string `json:"code" binding:"required,max=255"`
+	State string `json:"state" binding:"required,max=255"`
+}
+
+func CasdoorCallback(c *gin.Context) {
+	var loginUser CasdoorLoginUser
+	fmt.Println("CasdoorCallback called")
+	ok := BindAndValid(c, &loginUser)
+	if !ok {
+		return
+	}
+	endpoint := settings.ServerSettings.CasdoorEndpoint
+	clientId := settings.ServerSettings.CasdoorClientId
+	clientSecret := settings.ServerSettings.CasdoorClientSecret
+	certificate := settings.ServerSettings.CasdoorCertificate
+	organization := settings.ServerSettings.CasdoorOrganization
+	application := settings.ServerSettings.CasdoorApplication
+	if endpoint == "" || clientId == "" || clientSecret == "" || certificate == "" || organization == "" || application == "" {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": "Casdoor is not configured",
+		})
+	}
+	casdoorsdk.InitConfig(endpoint, clientId, clientSecret, certificate, organization, application)
+	token, err := casdoorsdk.GetOAuthToken(loginUser.Code, loginUser.State)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+		return
+	}
+	claims, err := casdoorsdk.ParseJwtToken(token.AccessToken)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+		return
+	}
+	u, err := model.GetUser(claims.Name)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+	}
+
+	userToken, err := model.GenerateJWT(u.Name)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+		"token":   userToken,
+	})
+}

+ 20 - 0
server/api/settings.go

@@ -1,9 +1,11 @@
 package api
 
 import (
+	"fmt"
 	"github.com/0xJacky/Nginx-UI/server/settings"
 	"github.com/gin-gonic/gin"
 	"net/http"
+	"net/url"
 )
 
 func GetSettings(c *gin.Context) {
@@ -39,3 +41,21 @@ func SaveSettings(c *gin.Context) {
 
 	GetSettings(c)
 }
+
+func GetCasdoorUri(c *gin.Context) {
+	endpoint := settings.ServerSettings.CasdoorEndpoint
+	clientId := settings.ServerSettings.CasdoorClientId
+	redirectUri := settings.ServerSettings.CasdoorRedirectUri
+	state := settings.ServerSettings.CasdoorApplication
+	fmt.Println(redirectUri)
+	if endpoint == "" || clientId == "" || redirectUri == "" || state == "" {
+		c.JSON(http.StatusOK, gin.H{
+			"uri": "",
+		})
+		return
+	}
+	encodedRedirectUri := url.QueryEscape(redirectUri)
+	c.JSON(http.StatusOK, gin.H{
+		"uri": fmt.Sprintf("%s/login/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s&state=%s&scope=read", endpoint, clientId, encodedRedirectUri, state),
+	})
+}

+ 3 - 0
server/router/routers.go

@@ -33,6 +33,9 @@ func InitRouter() *gin.Engine {
 		root.POST("/login", api.Login)
 		root.DELETE("/logout", api.Logout)
 
+		root.GET("/casdoor_uri", api.GetCasdoorUri)
+		root.POST("/casdoor_callback", api.CasdoorCallback)
+
 		// translation
 		root.GET("translation/:code", api.GetTranslation)
 

+ 37 - 23
server/settings/settings.go

@@ -16,19 +16,26 @@ var (
 )
 
 type Server struct {
-	HttpHost          string `json:"http_host"`
-	HttpPort          string `json:"http_port"`
-	RunMode           string `json:"run_mode"`
-	JwtSecret         string `json:"jwt_secret"`
-	NodeSecret        string `json:"node_secret"`
-	HTTPChallengePort string `json:"http_challenge_port"`
-	Email             string `json:"email"`
-	Database          string `json:"database"`
-	StartCmd          string `json:"start_cmd"`
-	CADir             string `json:"ca_dir"`
-	Demo              bool   `json:"demo"`
-	PageSize          int    `json:"page_size"`
-	GithubProxy       string `json:"github_proxy"`
+	HttpHost            string `json:"http_host"`
+	HttpPort            string `json:"http_port"`
+	RunMode             string `json:"run_mode"`
+	JwtSecret           string `json:"jwt_secret"`
+	NodeSecret          string `json:"node_secret"`
+	HTTPChallengePort   string `json:"http_challenge_port"`
+	Email               string `json:"email"`
+	Database            string `json:"database"`
+	StartCmd            string `json:"start_cmd"`
+	CADir               string `json:"ca_dir"`
+	Demo                bool   `json:"demo"`
+	PageSize            int    `json:"page_size"`
+	GithubProxy         string `json:"github_proxy"`
+	CasdoorEndpoint     string `json:"casdoor_endpoint"`
+	CasdoorClientId     string `json:"casdoor_client_id"`
+	CasdoorClientSecret string `json:"casdoor_client_secret"`
+	CasdoorCertificate  string `json:"casdoor_certificate"`
+	CasdoorOrganization string `json:"casdoor_organization"`
+	CasdoorApplication  string `json:"casdoor_application"`
+	CasdoorRedirectUri  string `json:"casdoor_redirect_uri"`
 }
 
 type Nginx struct {
@@ -49,16 +56,23 @@ type OpenAI struct {
 }
 
 var ServerSettings = Server{
-	HttpHost:          "0.0.0.0",
-	HttpPort:          "9000",
-	RunMode:           "debug",
-	HTTPChallengePort: "9180",
-	Database:          "database",
-	StartCmd:          "login",
-	Demo:              false,
-	PageSize:          10,
-	CADir:             "",
-	GithubProxy:       "",
+	HttpHost:            "0.0.0.0",
+	HttpPort:            "9000",
+	RunMode:             "debug",
+	HTTPChallengePort:   "9180",
+	Database:            "database",
+	StartCmd:            "login",
+	Demo:                false,
+	PageSize:            10,
+	CADir:               "",
+	GithubProxy:         "",
+	CasdoorEndpoint:     "",
+	CasdoorClientId:     "",
+	CasdoorClientSecret: "",
+	CasdoorCertificate:  "",
+	CasdoorOrganization: "",
+	CasdoorApplication:  "",
+	CasdoorRedirectUri:  "",
 }
 
 var NginxSettings = Nginx{