package ControllerOauth2 import ( "dsSso/Const" "dsSso/Const/DefaultConst" "dsSso/Controller/ControllerRecaptcha" "dsSso/Dao/DaoBaseGlobal" "dsSso/Model" "dsSso/Service/ServiceJoinApp" "dsSso/Service/ServiceLoginPerson" "dsSso/Utils/AesUtil" "dsSso/Utils/CommonUtil" "dsSso/Utils/ConfigUtil" "dsSso/Utils/ConvertUtil" "dsSso/Utils/RedisStorage" "dsSso/Utils/RedisUtil" "dsSso/Utils/RsaUtil" "dsSso/Utils/SsoUtil" "encoding/base64" "encoding/json" "fmt" "github.com/dchest/captcha" "github.com/gin-gonic/gin" "io/ioutil" "net/http" "strconv" "strings" "time" ) //模块的路由配置 func Routers(r *gin.RouterGroup) { rr := r.Group("") //列表 rr.GET("/authorize", authorizeGet) //注册POST接口,用于验证 r.POST("/authorize", authorizePost) //用于授权码换取access_token r.POST("/access_token", accessToken) //获取验证码 r.GET("/getCaptcha", getCaptcha) //获取验证码图片 r.GET("/getCaptchaPng", getCaptchaPng) //退出接口 r.GET("/logout", logout) return } // @Summary 获取验证码 // @Description 获取验证码(为了以后WEB服务器的集群扩展,将验证码保存到redis中,避免ip_hash) // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Success 200 {object} Model.Res // @Router /oauth2/getCaptcha [get] func getCaptcha(c *gin.Context) { //自定义redis为存储器 captcha.SetCustomStore(&ControllerRecaptcha.RedisStoreBean) d := struct { CaptchaId string }{ captcha.NewLen(4), } if d.CaptchaId != "" { c.JSON(http.StatusOK, Model.Res{ Code: http.StatusOK, Msg: "获取成功", Data: d.CaptchaId, }) } else { c.JSON(http.StatusOK, Model.Res{ Code: http.StatusInternalServerError, Msg: "获取失败", Items: nil, }) } } // @Summary 获取验证码图片 // @Description 获取验证码图片 // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Param captchaId query string true "captchaId" // @Success 200 {string} string // @Router /oauth2/getCaptchaPng [get] // @X-LengthLimit [{"captchaId":"20,20"}] func getCaptchaPng(c *gin.Context) { ControllerRecaptcha.ServeHTTP(c.Writer, c.Request) } /** 功能:产生授权码 作者:黄海 时间:2020-03-13 */ func generateAuthCode(context *gin.Context, identityId string, personId string, deviceId string, typeId string) { //1、生成加密串 r, _ := AesUtil.Encrypt([]byte(identityId + "_" + personId + "_" + deviceId + "_" + ConvertUtil.Int64ToString(CommonUtil.GetCurrentTimestamp()))) //2、转base64 encodeString := base64.RawURLEncoding.EncodeToString([]byte(r)) //3、写cookie context.SetCookie(ConfigUtil.AccessToken, encodeString, 0, "/", "", false, true) //如果是统一认证管理员登录 if identityId == "0" && personId == "0" { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusOK, Items: "/sso/static/ssoSystemList.html", }) return } else { //如果不是第三方系统跳转过来,而且还不是SSO管理员的情况下。 if context.PostForm("client_id") == "" && context.Query("client_id") == "" { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusOK, Msg: "不是第三方系统跳转过来,而且还不是SSO管理员", Items: "/sso/static/Error.html", }) return } //否则的话,返回授权码 req := context.Request resp := RedisStorage.OsinServer.NewResponse() defer resp.Close() if ar := RedisStorage.OsinServer.HandleAuthorizeRequest(resp, req); ar != nil { //标识验证通过 ar.Authorized = true ar.Expiration = 3600 * 2 //过期时间两小时 RedisStorage.OsinServer.FinishAuthorizeRequest(resp, req, ar) //用redis记录一下code--->personinfo的关系(黄海) personStr := "{\"identity_id\":\"" + identityId + "\",\"person_id\":\"" + personId + "\",\"device_id\":\"" + deviceId + "\"}" key := ConfigUtil.OAuth2RedisKeyPrefix + ":code_vs_person:" + resp.Output["code"].(string) RedisUtil.SET(key, personStr, 10*60*time.Second) } //返回数据 url := resp.URL code := resp.Output["code"].(string) state := resp.Output["state"].(string) //第三方的回调页面地址 oauthCallback := context.PostForm("oauth_callback") if oauthCallback == "" { oauthCallback = context.Query("oauth_callback") } //POST方式返回json数据 url = url + "?code=" + code + "&state=" + state + "&oauth_callback=" + oauthCallback if typeId == "POST" { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusOK, Items: url, }) return } else { //GET方式跳转回客户端的回调页 context.Redirect(302, url) return } } } // @Summary 获取access_token // @Description 获取access_token // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Param code query string false "code" // @Param refresh_token query string false "refresh_token" // @Success 200 {string} string // @Router /oauth2/access_token [post] func accessToken(context *gin.Context) { req := context.Request resp := RedisStorage.OsinServer.NewResponse() defer resp.Close() var identityId string var personId string var deviceId string //授权码 code := context.PostForm("code") if code != "" { //从redis中获取回来 personInfo, err := RedisUtil.GET(ConfigUtil.OAuth2RedisKeyPrefix + ":code_vs_person:" + code) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "输入的授权码不正确或已过期!", }) return } //还原结构体 m := make(map[string]interface{}) json.Unmarshal([]byte(personInfo), &m) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "还原人员信息结构体失败!", }) return } identityId, _ = m["identity_id"].(string) personId, _ = m["person_id"].(string) deviceId, _ = m["device_id"].(string) if ar := RedisStorage.OsinServer.HandleAccessRequest(resp, req); ar != nil { ar.Authorized = true ar.Expiration = 3600 * 2 RedisStorage.OsinServer.FinishAccessRequest(resp, req, ar) } //自己动手,丰衣足食 accessToken := resp.Output["access_token"].(string) expiresIn := resp.Output["expires_in"].(int32) refreshToken := resp.Output["refresh_token"].(string) tokenType := resp.Output["token_type"].(string) identityIdInt, _ := strconv.Atoi(identityId) //personIdInt, _ := strconv.Atoi(personId) //记录refresh_token与identity_id,person_id关系 personStr := "{\"identity_id\":\"" + identityId + "\",\"person_id\":\"" + personId + "\",\"device_id\":\"" + deviceId + "\"}" k := ConfigUtil.OAuth2RedisKeyPrefix + ":refresh_token_vs_person:" + refreshToken RedisUtil.SET(k, personStr, time.Hour*24*30) //30天的有效期 //返回json数据 context.JSON(http.StatusOK, gin.H{ "access_token": accessToken, "expires_in": expiresIn, "refresh_token": refreshToken, "token_type": tokenType, "identity_id": identityIdInt, "person_id": personId}) return } else { //刷新token clientRefreshToken := context.PostForm("refresh_token") if clientRefreshToken != "" { key := ConfigUtil.OAuth2RedisKeyPrefix + ":refresh_token_vs_person:" + clientRefreshToken personInfo, err := RedisUtil.GET(key) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "输入的刷新token不正确!", }) return } //还原结构体 m := make(map[string]interface{}) json.Unmarshal([]byte(personInfo), &m) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "还原人员信息结构体失败!", }) return } identityId, _ = m["identity_id"].(string) personId, _ = m["person_id"].(string) if ar := RedisStorage.OsinServer.HandleAccessRequest(resp, req); ar != nil { ar.Authorized = true RedisStorage.OsinServer.FinishAccessRequest(resp, req, ar) } accessToken := resp.Output["access_token"].(string) expiresIn := resp.Output["expires_in"].(int32) refreshToken := resp.Output["refresh_token"].(string) tokenType := resp.Output["token_type"].(string) identityIdInt, _ := strconv.Atoi(identityId) personIdInt, _ := strconv.Atoi(personId) //删除旧的refresh_token RedisUtil.DEL(key) //返回json数据 context.JSON(http.StatusOK, gin.H{ "access_token": accessToken, "expires_in": expiresIn, "refresh_token": refreshToken, "token_type": tokenType, "identity_id": identityIdInt, "person_id": personIdInt}) return } } } // @Summary 验证post // @Description 验证post // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Param captchaId formData string true "验证码id" // @Param value formData string true "验证码值" // @Param username formData string true "用户名" // @Param password formData string true "密码" // @Param device_id formData string true "设备ID" // @Param redirect_uri formData string false "第三方系统的接口回调地址" // @Param client_id formData string false "第三方系统的ID" // #Param oauth_callback formData string false "第三方系统的回调页面地址" // @Success 200 {object} Model.Res // @Router /oauth2/authorize [post] // @X-IntLimit ["device_id"] // @X-EmptyLimit ["username","password","captchaId","value"] func authorizePost(context *gin.Context) { var identityId string var personId string var success bool var remainCount int //1、验证码 captchaId := context.PostForm("captchaId") value := context.PostForm("value") //2、用户名和密码 username := context.PostForm("username") encryptPwd := context.PostForm("password") //3、设备号 deviceId := context.PostForm("device_id") //4、校验函数根据redis的读取结果进行检查 var redisStore ControllerRecaptcha.RedisStore if redisStore.VerifyString(captchaId, value) { //对前端AES加密过的密码进行解密 b, err := base64.StdEncoding.DecodeString(encryptPwd) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "密码不是系统允许的base64方式!", }) return } decryptPwd, err := RsaUtil.RsaDecrypt(b) if err != nil { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "密码不是系统允许的加密方式!", }) return } //调用service层的用户名和密码校验办法判断是不是允许登录 ip := context.ClientIP() success, identityId, personId, _, remainCount = ServiceLoginPerson.Login(username, string(decryptPwd), ip) if !success { //三次以上不提示 if remainCount >= 3 { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "用户名密码不正确或已禁用!", }) } else if remainCount > 0 { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "用户名密码不正确或已禁用!您还可以尝试" + CommonUtil.ConvertIntToString(remainCount) + "次,超出次数限制后,账号将被禁用2小时!", }) } else { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "账号已处于禁用状态,请稍后再试!", }) } return } } else { context.JSON(http.StatusOK, Model.Res{ Code: http.StatusNotImplemented, Msg: "验证码不正确!", }) return } //验证通过的话,返回授权码 generateAuthCode(context, identityId, personId, deviceId, "POST") } // @Summary 登录验证跳转路由 // @Description 登录验证跳转路由 // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Param client_id query string true "client_id" // @Param redirect_uri query string true "redirect_uri" // @Param device_id query string true "device_id" // @Param oauth_callback query string true "第三方系统回跳的页面地址" // @Success 200 {string} string // @Router /oauth2/authorize [get] // @X-EmptyLimit ["client_id","redirect_uri","device_id","oauth_callback"] func authorizeGet(context *gin.Context) { //客户端的client_id var paraClientId = context.Query("client_id") //回调接口地址 var redirectUri = context.Query("redirect_uri") //回调的页面地址 var oauthCallback = context.Query("oauth_callback") //检查这个client_id是不是经过授权? client, err := RedisStorage.OAuth2RedisStorage.GetClient(paraClientId) if err != nil { msg := "传入的client_id不在系统的允许范围内!" context.JSON(http.StatusOK, map[string]interface{}{"success": false, "msg": msg}) return } var sysUrl = client.GetRedirectUri() if strings.Index(sysUrl, redirectUri) < 0 { msg := "回调地址与系统中保存的不一致!" context.JSON(http.StatusOK, map[string]interface{}{"success": false, "msg": msg}) return } var deviceId = context.Query("device_id") //注册验证是否登录的接口,这个GET接口是直接跳转走的 //此处判断人员是否存在本站下的session,如果有的话 var identityId = DefaultConst.IdentityId var personId = DefaultConst.PersonId SsoSessionId := SsoUtil.ReadSsoCookie(context) if SsoSessionId != "" { //取出UserData identityId, personId, _ = SsoUtil.AnalyzeSessionId(SsoSessionId) if identityId != "" && personId != "" { //为其生成授权码,并返回给客户端系统 generateAuthCode(context, identityId, personId, deviceId, "GET") return } } //如果没有本domain下的cookie,那么跳转到登录页 url := "/sso/static/index.html?redirect_uri=" + redirectUri + "&response_type=code&client_id=" + paraClientId + "&oauth_callback=" + oauthCallback context.Redirect(302, url) } // @Summary 退出统一认证 // @Description 退出统一认证 // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Param redirect_uri query string true "redirect_uri" // @Produce json // @Success 200 {string} string // @Router /oauth2/logout [get] func logout(context *gin.Context) { //取出现在的cookie中的accessToken accessToken := SsoUtil.ReadSsoCookie(context) //获取所有接入系统的 list, _ := ServiceJoinApp.GetJoinAppList(1, Const.Int32Max, "") for i := range list { //每个系统的退出地址 logoutUri := list[i]["logout_uri"].(string) //如果注册了退出地址的话 if len(logoutUri) > 0 { //开启协程进行调用 go func() { resp, err := http.Get(logoutUri + "?access_token=" + accessToken) if err != nil { fmt.Println(err) return } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) }() } } //清除cookie SsoUtil.DeleteSsoCookie(context) //判断来源是内网还是外网,是指客户端的IP地址,即浏览器的IP ipStart := strings.Split(context.Request.Host, ".")[0] isIntranetIP := strings.Index("10,192,172", ipStart) //外网 var s string if isIntranetIP < 0 { s = "defaultIndexWwUrl" } else { //内网 s = "defaultIndexNwUrl" } //跳转回去,系统默认统一的回调首页 list, err := DaoBaseGlobal.GetGlobalInfoByValue(s) if err != nil || len(list) == 0 { msg := "获取全局变量defaultIndexUrl失败!" context.JSON(http.StatusOK, map[string]interface{}{"success": false, "msg": msg}) return } globalValue := list[0]["global_value"].(string) context.Redirect(302, globalValue) return } // @Summary 重置错误重试次数限制 // @Description 重置错误重试次数限制 // @Tags 登录验证类 // @Accept application/x-www-form-urlencoded // @Produce json // @Param userName query string true "登录用的用户名" // @Success 200 {string} string // @Router /oauth2/resetRemainCount [get] // @X-EmptyLimit ["userName"] func resetRemainCount(context *gin.Context) { userName := context.Query("userName") RedisUtil.DEL(Const.RemainCountRedisPrefix + userName) msg := "成功清除禁用标识!" context.JSON(http.StatusOK, map[string]interface{}{"success": true, "msg": msg}) return }