美文网首页
Vue+Golang接入KeyCloak

Vue+Golang接入KeyCloak

作者: xuing | 来源:发表于2021-09-16 17:52 被阅读0次

    原文发布在本人博客 https://xuing.cn/program/vue-golang-keycloak.html

    Vue+Golang接入KeyCloak

    Vue+Golang接入KeyCloak实现简单的角色划分、权限校验。

    本人Golang苦手,也是第一次接触Keycloak。网上资料太少,我的方案大概率非最佳实践。仅供参考。欢迎批评意见。

    接入预期

    本次实践将达到以下几个目的:

    1. 前端Vue接入KeyCloak,必须登录后才进行渲染。
    2. 后端Golang Beego框架接入Keycloak。使用前端传过来的Authorization进行鉴权。
    3. 区分普通用户和管理员两种角色。

    KeyCloak搭建、配置

    最方便的搭建方式当然就是用Docker了。

    docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2

    1. 登录管理页面。创建Realme:demo

      我的理解一个Realm对应一个应用。

    2. 首先建立前端使用的Client:demo-front

      client,用于和keycloak进行通信。

      前端无需特别配置。使用Access Type为public的(即不需要ClientSecret)默认配置即可。

      配置 Valid Redirect URIs 允许登录成功后的重定向地址。测试时可以使用*。来允许任意地址。

    3. 接下来再建立后端使用的Clientdemo-back

      1. 配置Access Type为confidential。在Credentials中Tab记录下生成的Secret。供后续使用。
      2. 为了能够有权限查询用户的角色信息,首先开启Service Accounts。在新出现的Service Account Roles Tab中,增加Client Roles。我这里没有做测试,把能增加的权限都加进去了。可能只需要增加realm-managementquery-users权限即可(未测试)。
    4. 新建Roles。

      普通用户角色:demo_user_role

      管理用角色:demo_admin_role

    5. 创建用户,本文不涉及用户注册的操作, 就直接在后台创建两个用户再分别分配上角色就好了。demo-user,demo-admin

    Vue接入

    VUE的接入文章还是挺多的。这里简略过一下。

    1. 安装导入vue-keycloak-js

    2. main.js中全局引入

      Vue.use(keycloak, {
        init: {
          onLoad: process.env.VUE_APP_KEYCLOAK_ONLOAD,
          checkLoginIframe: false // 防止登陆后重复刷新
        },
        config: {
          'realm': process.env.VUE_APP_KEYCLOAK_REALM,
          'url': process.env.VUE_APP_KEYCLOAK_URL, // auth-server-url
          'clientId': process.env.VUE_APP_KEYCLOAK_CLIENTID, // resource
          // 'credentials': {
          //   'secret': process.env.VUE_APP_KEYCLOAK_CLIENT_SECRET // clientSecret
          // },
        },
        onReady: (keycloak) => {
          new Vue({
            el: '#app',
            router,
            store,
            render: h => h(App)
          })
        }
      })
      

      配置文件参考

      VUE_APP_KEYCLOAK_URL = https://keycloak地址/auth
      VUE_APP_KEYCLOAK_REALM = demo
      VUE_APP_KEYCLOAK_CLIENTID = demo-front
      VUE_APP_KEYCLOAK_ONLOAD = login-required
      

    Golang接入

    golang接入keycloak,这里使用gocloak库。我使用的是v8版本,目前已经有v9了。我这里是手动维护了一个JWT token 用于和keycloak进行通信,后续可能有更简单的方案。

    https://github.com/Nerzal/gocloak

    初始化Client

    因为使用的是confidential的访问模式,我们需要登录demo-backclient到keycloak。并且维护登录状态。

    初始化代码如下:

    声明用户类型常量,维护client需要的相关变量并提供刷新(登录)函数。

    models/user.go

    type UserType int
    const (
        ApplicationAdminType UserType = iota
        SuperAdminType                //超级管理员
        UnAuthorizedUserType          //未授权用户
    )
    
    var (
        userId    string
        client    = gocloak.NewClient(conf.AppConfig.KeycloakUrl)
        clientJWT *gocloak.JWT
        // retrospecTokenResult存放了clientJWT的过期时间(Exp)等
        retrospecTokenResult *gocloak.RetrospecTokenResult
    )
    
    /**
     * 登录到client,并获取clientJWT与retrospecTokenResult。
     */
    func updateClient() {
        var err error
        clientJWT, err = client.LoginClient(context.Background(), conf.AppConfig.KeycloakClientId, conf.AppConfig.KeycloakClientSecret, conf.AppConfig.KeycloakRealm)
        if err != nil || clientJWT == nil {
            tools.Panic(tools.ErrCodeInitKeyCloakFailed, "failed to Login KeyCloak Client ", err)
        }
        retrospecTokenResult, err = client.RetrospectToken(context.Background(), clientJWT.AccessToken, conf.AppConfig.KeycloakClientId, conf.AppConfig.KeycloakClientSecret, conf.AppConfig.KeycloakRealm)
        if err != nil || retrospecTokenResult == nil {
            tools.Panic(tools.ErrCodeInitKeyCloakFailed, "failed to Retrospect KeyCloak Token ", err)
        }
    }
    
    func init() {
        updateClient()
        ...
    

    路由鉴权

    为api接口增加鉴权,获取Authorization Header中的AccessToken,并发送给Keycloak,获取用户的基本信息,主要是Sub(即用户id)。

    再遍历User的Role信息,确定用户角色。

    filter/auth.go

    // 初始化,添加路由鉴权
    func init() {
        beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
            AllowAllOrigins:  true,
            AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
            AllowHeaders:     []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
            ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
            AllowCredentials: true,
        }))
        
        beego.InsertFilter("/v1/api/*", beego.BeforeRouter, authApi)
        
        //....
    }
    
    // 校验函数
    func authApi(ctx *context.Context) {
        AccessToken := ctx.Input.Header("Authorization")
        if AccessToken == "" {
            ctx.Output.JSON(map[string]interface{}{
                "status": http.StatusUnauthorized, "description": http.StatusText(http.StatusUnauthorized)},
                false, false)
            return
        }
    
        if strings.Index(AccessToken, "Bearer ") == 0 {
            AccessToken = AccessToken[7:]
        }
    
        UserInfo, err := models.GetUserInfo(AccessToken)
        if err != nil || UserInfo == nil {
            log.Println(err)
            ctx.Output.JSON(map[string]interface{}{
                "status": http.StatusUnauthorized, "description": http.StatusText(http.StatusUnauthorized)},
                false, false)
            return
        }
    
        userType, err := models.GetUserRole(*UserInfo.Sub)
        if err != nil || userType == models.UnAuthorizedUserType {
            log.Println(err)
            ctx.Output.JSON(map[string]interface{}{
                "status": http.StatusUnauthorized, "description": http.StatusText(http.StatusUnauthorized) + " 未查询到使用授权信息。"},
                false, false)
            return
        }
        ctx.Input.SetData("UserType", userType)
    
        ctx.Input.SetData("UserId", *UserInfo.Sub)
        
        // 具体业务代码,如获取用户名、根据用户角色进行不同的鉴权处理。
        // ....
    }
    
    
    

    具体实现

    获取用户信息和获取用户角色的实现如下。代码可根据业务进行调整。

    models/user.go

    // 获取用户基础信息
    func GetUserInfo(accessToken string) (user *gocloak.UserInfo, err error) {
        user, err = client.GetUserInfo(context.Background(), accessToken, conf.AppConfig.KeycloakRealm)
        return
    }
    
    // 获取用户角色信息
    func GetUserRole(userId string) (userType UserType, err error) {
        userType = UnAuthorizedUserType
        //判断是否过期
        if int64(*retrospecTokenResult.Exp) < time.Now().Unix() {
            updateClient()
        }
        mappingsRepresentation, err := client.GetRoleMappingByUserID(context.Background(), clientJWT.AccessToken, conf.AppConfig.KeycloakRealm, userId)
        for _, v := range *mappingsRepresentation.RealmMappings {
            if *v.Name == "demo_admin_role" {
                userType = SuperAdminType
                break
            }
            if *v.Name == "demo_user_role" {
                userType = ApplicationAdminType
            }
        }
        return userType, err
    }
    

    维护client的JWT Token的任务,我直接写到获取用户角色这里了。我这里测试,获取用户基础信息的话,是不需要client的Access Token的。

    后记

    目前的实现是能满足我的业务需求呢,但keycloak的强大之处,我可能还远远没有用上。

    希望能提供一些帮助 hhhh。

    相关文章

      网友评论

          本文标题:Vue+Golang接入KeyCloak

          本文链接:https://www.haomeiwen.com/subject/hoxdgltx.html