# casdoor 用户认证使用 ## 安装 - 编译 1. [下载源码](https://github.com/casdoor/casdoor) 2. 编译后端服务:`go build .` 3. 编译前端:`yarn build` - 配置 编写Dockerfile文件: ```sh FROM alpine RUN echo -e https://mirrors.ustc.edu.cn/alpine/v3.15/main > /etc/apk/repositories \ && cat /etc/apk/repositories \ # 设置时区为上海 && apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && apk del tzdata \ # 解决apline 运行编译后的执行文件 not found错误 # 由于alpine镜像使用的是musl libc而不是gnu libc,/lib64/ 是不存在的。但他们是兼容的,可以创建个软连接 && mkdir /lib64 \ && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 WORKDIR / ADD web web ADD casdoor casdoor EXPOSE 8000 ENTRYPOINT ["./casdoor"] ``` 编写镜像构建脚本: ```sh #!/bin/bash # 命名镜像 repository_image="registry.cn-chengdu.aliyuncs.com/infish/pack-casdoor-auth:v1.0.0" # 删除本地已存在的镜像 docker rmi $repository_image # 创建本地镜像 docker build -t $repository_image . # push到镜像仓库,需要登陆对应docker仓库账号 docker push $repository_image ``` 编写docker-compose.yaml文件: ```sh version: '3.8' # 网络 networks: default: name: default-network external: true services: auth-casdoor: image: "registry.cn-chengdu.aliyuncs.com/infish/pack-casdoor-auth:v1.0.0" restart: always ports: - 36002:8000 volumes: - ./conf:/conf depends_on: - auth-mysql auth-mysql: restart: always # 5.7不支持中文 image: mysql:8 volumes: - /data/auth/mysql:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: auth2023 MYSQL_DATABASE: casdoor ports: - 33306:3306 ``` conf/app.conf文件: ```sh ppname = casdoor httpport = 8000 runmode = dev copyrequestbody = true driverName = mysql dataSourceName = root:auth2023@tcp(auth-mysql:3306)/ dbName = casdoor # tableNamePrefix = # showSql = false # redisEndpoint = # defaultStorageProvider = # isCloudIntranet = false # authState = "casdoor" # socks5Proxy = "127.0.0.1:10808" # verificationCodeTimeout = 10 # initScore = 2000 # logPostOnly = true # origin = # staticBaseUrl = "https://auth3dqueen.oss-cn-beijing.aliyuncs.com/static" # isDemoMode = false # batchSize = 100 # ldapServerPort = 389 # languages = en,zh,es,fr,de,id,ja,ko,ru,vi # quota = {"organization": -1, "user": -1, "application": -1, "provider": -1} ``` 启动服务:`docker compose up -d` 配置nginx: ```conf server { listen 80; listen [::]:80; server_name auth.3dqueen.cloud; rewrite ^(.*) https://$server_name$1 permanent; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name auth.3dqueen.cloud; #root /var/www/auth; client_max_body_size 5M; ssl_certificate "cert/9743199_auth.3dqueen.cloud.pem"; ssl_certificate_key "cert/9743199_auth.3dqueen.cloud.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect off; proxy_pass http://127.0.0.1:36002; } } ``` ## 配置应用 访问nginx配置的域名,登录后配置组织和应用:`账号:admin 密码:123` 组织配置:![组织配置](组织.png) 应用配置:![应用配置](应用.png) ## 认证 配置证书:![证书配置](证书.png) 下载证书、配置到后端: app.yaml: ```yaml ... Auth: endpoint: https://auth.3dqueen.cloud clientId: 9f22d6616ae14fe59dc4 clientSecret: b0fae13ca7e7eb1e4e6dc8fa50902eb7b6035049 certificate: token_jwt_key.pem organizationName: org_3dqueen_cloud applicationName: app_boxcost ``` Dockerfile文件中添加: ```Dockerfile ... # 下载的证书 ADD token_jwt_key.pem token_jwt_key.pem ... ``` 下载使用[casdoor-go-sdk](https://github.com/casdoor/casdoor-go-sdk) 配置回调:`api/calback.go` ```go // https://auth.3dqueen.cloud/login/oauth/authorize?client_id=9f22d6616ae14fe59dc4&redirect_uri=https://www.3dqueen.cloud/box/v1/boxcost/callback&response_type=code&scope=openid&state=STATE // 需要在第三方提供商配置回调 https://auth.3dqueen.cloud/callback func callback(c *gin.Context, apictx *ApiSession) (interface{}, error) { authConf := apictx.Svc.Conf.Auth pemByte, err := os.ReadFile(authConf.Certificate) if err != nil { return nil, err } casdoorsdk.InitConfig(authConf.Endpoint, authConf.ClientId, authConf.ClientSecret, string(pemByte), authConf.OrganizationName, authConf.ApplicationName) token, err := casdoorsdk.GetOAuthToken(c.Query("code"), c.Query("state")) if err != nil { fmt.Println(err) } fmt.Println(token.AccessToken) claims, err := casdoorsdk.ParseJwtToken(token.AccessToken) if err != nil { fmt.Println(err) } fmt.Println(claims) c.Redirect(http.StatusFound, "https://auth.3dqueen.cloud") // claims.AccessToken = token.AccessToken return nil, nil } ``` password方式获取token:`api/auth.go` ```go func AuthToken(c *gin.Context, apictx *ApiSession) (interface{}, error) { authConf := apictx.Svc.Conf.Auth // pemByte, err := os.ReadFile(authConf.Certificate) // if err != nil { // return nil, err // } // casdoorsdk.InitConfig(authConf.Endpoint, authConf.ClientId, authConf.ClientSecret, string(pemByte), authConf.OrganizationName, authConf.ApplicationName) data := &AuthTOkenReq{ GrantType: "password", ClientId: authConf.ClientId, ClientSecret: authConf.ClientSecret, UserName: "sunsheng", Password: "ssxhyw2515", } dataByte, err := json.Marshal(data) if err != nil { return nil, err } fmt.Println(dataByte) // 发送POST请求并将数据作为JSON提交 req, err := http.NewRequest("POST", fmt.Sprintf("%s/%s", authConf.Endpoint, "api/login/oauth/access_token"), bytes.NewBuffer(dataByte)) if err != nil { panic(err) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var buf bytes.Buffer _, err = buf.ReadFrom(resp.Body) if err != nil { panic(err) } buf.WriteTo(c.Writer) // responseStr := buf.String() // 打印响应内容 // fmt.Println(responseStr) return nil, nil } ``` ## 权限控制 配置权限模型:![权限模型](模型.png) 配置权限:![权限模型](权限.png) jwt中间件:`middleware/jwt.go` ```go package middleware import ( "fmt" "net/http" "strings" "time" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" "github.com/gin-gonic/gin" ) // JWTAuthMiddleware 基于JWT的认证中间件--验证用户是否登录 func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { authHeader := c.Request.Header.Get("authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{ "code": 2003, "msg": "请求头中auth为空", }) c.Abort() return } // 按空格分割 parts := strings.Split(authHeader, ".") if len(parts) != 3 { c.JSON(http.StatusUnauthorized, gin.H{ "code": 2004, "msg": "请求头中auth格式有误", }) c.Abort() return } mc, err := casdoorsdk.ParseJwtToken(authHeader) fmt.Println(mc.VerifyExpiresAt(time.Now(), false)) if err != nil { fmt.Println(err) c.JSON(http.StatusUnauthorized, gin.H{ "code": 2005, "msg": "无效的Token", }) c.Abort() return } // m := mc.(jwt.MapClaims) // 将当前请求的username信息保存到请求的上下文c上 c.Set("uid", mc.User.Id) c.Set("username", mc.User.Name) c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息 } } ``` 权限控制中间件:`middleware/pc.go` ```go package middleware import ( "boxcost/conf" "fmt" "net/http" "strings" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" "github.com/gin-gonic/gin" ) func PermissionControlMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // 验证该用户是否可以访问该资源 permission := strings.Replace(c.Request.URL.String(), "/", "_", -1) id := fmt.Sprintf("%s/%s%s", conf.AppConfig.Auth.OrganizationName, strings.ToLower(c.Request.Method), permission) fmt.Println(id) ok, err := casdoorsdk.Enforce(&casdoorsdk.PermissionRule{ Ptype: "p", V0: fmt.Sprintf("%s/%s", conf.AppConfig.Auth.OrganizationName, c.GetString("username")), // 请求实体 V1: c.Request.URL.String(), // 请求资源主体 // /book/* V2: strings.ToLower(c.Request.Method), // 请求方法 Id: id, }) // roles if err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "code": -1, "msg": err.Error(), }) c.Abort() return } if !ok { c.JSON(http.StatusForbidden, gin.H{ "code": -1, "msg": "没有权限访问", }) c.Abort() return } c.Next() } } ``` 使用中间件:`api/api.go` ```go ... // GETJWT http Get 请求 func (g GinRouter) GETJWT(path string, httpHandler JWTHander) { g.group.GET(path, middleware.JWTAuthMiddleware(), middleware.PermissionControlMiddleware(), ResultJWTWrapper(httpHandler, g.svc)) } // POSTJWT http POST 请求 func (g GinRouter) POSTJWT(path string, httpHandler JWTHander) { g.group.POST(path, middleware.JWTAuthMiddleware(), middleware.PermissionControlMiddleware(), ResultJWTWrapper(httpHandler, g.svc)) } ... ``` 测试:`api/user.go` ```go package api import ( "afina/conf" "fmt" "time" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" "github.com/gin-gonic/gin" ) func User(r *GinRouter) { r.POSTJWT("/user/create", CreateUser) } func CreateUser(c *gin.Context, apictx *ApiSession) (interface{}, error) { var user casdoorsdk.User err := c.ShouldBindJSON(&user) if err != nil { fmt.Println(err) return nil, err } user.SignupApplication = conf.AppConfig.Auth.ApplicationName user.CreatedTime = time.Now().Format("2006-01-02T15:04:05-07:00") user.Type = "normal-user" return casdoorsdk.AddUser(&user) } ``` 测试api:![测试api](测试api.png) ## 其他 配置短信:![短信配置](短信配置.png) 第三方登录:![第三方登录配置](第三方登录.png) 对象存储:![对象存储配置](对象存储.png)