feat: added casdoor sso support (#204)

This commit is contained in:
Jray 2023-11-25 23:12:51 +08:00 committed by GitHub
parent 34289150fa
commit 7fe3517afe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 26 deletions

View file

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

View file

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

View file

@ -8,6 +8,8 @@ import {Form, message} from 'ant-design-vue'
import auth from '@/api/auth' import auth from '@/api/auth'
import install from '@/api/install' import install from '@/api/install'
import SetLanguage from '@/components/SetLanguage/SetLanguage.vue' import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
import http from '@/lib/http'
import {onMounted} from 'vue'
const thisYear = new Date().getFullYear() const thisYear = new Date().getFullYear()
@ -70,6 +72,37 @@ watch(() => gettext.current, () => {
clearValidate() 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> </script>
<template> <template>
@ -105,6 +138,9 @@ watch(() => gettext.current, () => {
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </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"> <div class="footer">
<p>Copyright © 2020 - {{ thisYear }} Nginx UI</p> <p>Copyright © 2020 - {{ thisYear }} Nginx UI</p>
Language Language

3
go.mod
View file

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

6
go.sum
View file

@ -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/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/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/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 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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= 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-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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/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.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View file

@ -1,7 +1,10 @@
package api package api
import ( import (
"fmt"
"github.com/0xJacky/Nginx-UI/server/model" "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" "github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"net/http" "net/http"
@ -55,3 +58,62 @@ func Logout(c *gin.Context) {
} }
c.JSON(http.StatusNoContent, nil) 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,
})
}

View file

@ -1,9 +1,11 @@
package api package api
import ( import (
"fmt"
"github.com/0xJacky/Nginx-UI/server/settings" "github.com/0xJacky/Nginx-UI/server/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"net/url"
) )
func GetSettings(c *gin.Context) { func GetSettings(c *gin.Context) {
@ -39,3 +41,21 @@ func SaveSettings(c *gin.Context) {
GetSettings(c) 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),
})
}

View file

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

View file

@ -29,6 +29,13 @@ type Server struct {
Demo bool `json:"demo"` Demo bool `json:"demo"`
PageSize int `json:"page_size"` PageSize int `json:"page_size"`
GithubProxy string `json:"github_proxy"` 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 { type Nginx struct {
@ -59,6 +66,13 @@ var ServerSettings = Server{
PageSize: 10, PageSize: 10,
CADir: "", CADir: "",
GithubProxy: "", GithubProxy: "",
CasdoorEndpoint: "",
CasdoorClientId: "",
CasdoorClientSecret: "",
CasdoorCertificate: "",
CasdoorOrganization: "",
CasdoorApplication: "",
CasdoorRedirectUri: "",
} }
var NginxSettings = Nginx{ var NginxSettings = Nginx{