|
|
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
|
|
|
}
|