使用Go语言开发SSO系统beego+jwt

简介

SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。

机制

当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。

项目介绍

  • web前端页面使用的是流行web框架Beego
  • 生成认证票据ticket是 jwt-go
  • 用户数据数据库校验读取是 Beego自带的orm
  • 允许所有域名可以访问是 cors

web 登录页面

  <div class="content">
    <div class="title">
      <span class="monileHidden">认证登录span>
      <span class="pcHidden">员工登录平台span>
    div>
    <form action="" method="POST" id="login-form">
      <p>
        <span>span><input type="text" id="user" placeholder="请输入工号/域账号" value="" />
      p>
      <p>
        <span>span><input type="password" id="psw" placeholder="请输入密码" value="" />
      p>

      <div id="warn"><span>!span><span id="warnText">span>div>
      <div class="submit" onClick="login()">登录div>
    form>

  div>

web 登录成功页面

     <div class="content">
            <div class="title">
                <span class="monileHidden">认证登录span>
                <span class="pcHidden">集团员工登录平台span>
            div>
            <div class="successLogin">
                <span>span>
                <h3>登录成功h3>
                <p>您已成功登录授权系统p>
                <p>当您想要退出时,请关闭您的浏览器p>
            div>
        div>

通常情况 是不需要登录成功页面的,因为用户进行账号密码验证后,直接跳转到对方项目

点击事件

  $(function () {
    $(".layerBox").on("click", ".radio-name", function () {
      $(this).parent().find("input").click();
    })
    $("#loginform").attr("action", location.href);
    window.document.onkeydown = function (evt) {
      evt = window.event || evt;
      if (evt.keyCode == 13) {
        login();
      }
    }
  });

  function login() {
    var warn = document.getElementById('warn');
    var user = document.getElementById('user');
    var psw = document.getElementById('psw');

    var text = !user.value && !psw.value ? '请输入账号和密码!' : !user.value ? '请输入账号!' : !psw.value ? '请输入密码!' : "";

    if (text) {
      document.getElementById("warnText").innerHTML = text;
      warn.style.display = 'block';
      return;
    }
    var options = {
      userName: user.value,
      password: psw.value,
      service: "{{.service}}",
      renew: false,
      returnurl: ""
    };
    if ($("#service").length > 0) {
      options.service = $("#service").val();
    }
    if ($("#renew").length > 0) {
      options.renew = $("#renew").val();
    }
    if ($("#returnurl").length > 0) {
      options.returnurl = $("#returnurl").val();
    }
    else {
      warn.style.display = 'none';
      $.post("/login", options, function (data) {
        $(".submit").html("登录");
        if (data.success == 0) {
          location.href = data.service;
        }  else {
          ShowMsg(data.Message);
        }
      });
    }
  }
  function ShowMsg(title) {
    $(".submit").html("登录");
    $("#warnText").html(title);
    $("#warn").css("display", "block");
  }


{{.service}} 是Beego使用模板渲染后端传给前端的数据

Json Web Token 生成token篇

func CreateToken(userName string) string {
	token := jwt.New(jwt.SigningMethodHS256)
	claims := make(jwt.MapClaims)
	//添加令牌关键信息
	Tokenexp, _ := strconv.Atoi(beego.AppConfig.String("Tokenexp"))
	//添加令牌期限
	claims["exp"] = time.Now().Add(time.Hour * time.Duration(Tokenexp)).Unix()
	claims["iat"] = time.Now().Unix()
	claims["userName"] = userName
	token.Claims = claims
	tokenString, _ := token.SignedString([]byte(beego.AppConfig.String("TokenSecrets")))
	return tokenString
}

生成Token 需要签名TokenSecrets
和过期时间Tokenexp
这个你可以先在配置中写好,另外在生成令牌中带上用户信息,
使用类似claims[“userName”]

Json Web Token 校验token篇

// 校验token是否有效 返回参数
func CheckToken(tokenString string) string {
	userName := ""
	token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
		return []byte(beego.AppConfig.String("TokenSecrets")), nil
	})

	if token != nil && token.Valid {
		claims, _ := token.Claims.(jwt.MapClaims)
		userName = claims["userName"].(string)
	}
	return userName
}

我返回的是用户名

beego访问流程篇

1.用户访问登录页面带上域名
http://127.0.0.1:8080/login?service=http://www.xxx.com.cn/ceo_data
2.账户密码验证成功后,会跳转到service中,并带上ticket
http://www.xxxx.com.cn/ceo_data?ticket=xxxxxxxx
3.第三方系统只需要通过接口”/serviceValidate“ 验证就可以判断是否登录,并且能得到用户信息哦。

路由

	beego.Router("/", &controllers.MainController{})
	beego.Router("/login", &controllers.MainController{})                      //授权登录
	beego.Router("/Success", &controllers.SuccessController{})                 //登录成功
	beego.Router("/serviceValidate", &controllers.ServiceValidateController{}) //验证登录信息

拦截请求,并允许所有域名可访问(cors)

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("/*", beego.BeforeRouter, func(ctx *context.Context) {
		cookie, err := ctx.Request.Cookie("TOKEN")
		fmt.Print(strings.Index(ctx.Request.RequestURI, "/login"))
		if strings.Index(ctx.Request.RequestURI, "/login") >= 0 || strings.Index(ctx.Request.RequestURI, "/static") >= 0 {

		} else if err != nil || unit.CheckToken(cookie.Value) == "" {
			ctx.Redirect(302, "/login")
		}

	})

用户密码校验逻辑

func (this *MainController) Post() {
	//获取service
	login_name := this.GetString("userName")
	password := this.GetString("password")
	service := this.GetString("service")
	//returnurl := this.GetString("returnurl")
	//验证用户名密码
	user, err := unit.AuthenticateUserForLogin(login_name, password)
	if user == nil {
		this.Data["json"] = map[string]interface{}{"success": -1, "message": err}
		this.ServeJSON()
		return
	}
	tokenString := unit.CreateToken(login_name)
	//请求中head中设置token
	this.Ctx.Output.Header("TOKEN", tokenString)
	//设置cookie 名称,值,时间,路径
	this.Ctx.SetCookie("TOKEN", tokenString, "3600", "/")
	this.Ctx.SetCookie("USERID", user.Id, "3600", "/")
	this.Data["json"] = map[string]interface{}{"success": 0, "msg": "登录成功", "user": user, "service": service}
	this.ServeJSON()
}

源码 https://github.com/harryluo163/go-sso

你可能感兴趣的:(go)