|
@@ -0,0 +1,454 @@
|
|
|
+# 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`
|
|
|
+
|
|
|
+组织配置:
|
|
|
+应用配置:
|
|
|
+
|
|
|
+## 认证
|
|
|
+
|
|
|
+配置证书:
|
|
|
+
|
|
|
+下载证书、配置到后端:
|
|
|
+
|
|
|
+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
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 权限控制
|
|
|
+
|
|
|
+配置权限模型:
|
|
|
+配置权限:
|
|
|
+
|
|
|
+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:
|
|
|
+
|
|
|
+## 其他
|
|
|
+
|
|
|
+配置短信:
|
|
|
+第三方登录:
|
|
|
+对象存储:
|