github.com/infraboard/keyauth@v0.8.1/apps/token/security/checker.go (about)

     1  package security
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/infraboard/mcube/app"
     9  	"github.com/infraboard/mcube/cache"
    10  	"github.com/infraboard/mcube/exception"
    11  	"github.com/infraboard/mcube/logger"
    12  	"github.com/infraboard/mcube/logger/zap"
    13  
    14  	"github.com/infraboard/keyauth/apps/domain"
    15  	"github.com/infraboard/keyauth/apps/ip2region"
    16  	"github.com/infraboard/keyauth/apps/session"
    17  	"github.com/infraboard/keyauth/apps/token"
    18  	"github.com/infraboard/keyauth/apps/user"
    19  )
    20  
    21  // NewChecker todo
    22  func NewChecker() (Checker, error) {
    23  	c := cache.C()
    24  	if c == nil {
    25  		return nil, fmt.Errorf("denpence cache service is nil")
    26  	}
    27  
    28  	return &checker{
    29  		domain:    app.GetGrpcApp(domain.AppName).(domain.ServiceServer),
    30  		user:      app.GetGrpcApp(user.AppName).(user.ServiceServer),
    31  		session:   app.GetGrpcApp(session.AppName).(session.ServiceServer),
    32  		cache:     c,
    33  		ip2Regoin: app.GetInternalApp(ip2region.AppName).(ip2region.Service),
    34  		log:       zap.L().Named("Login Security"),
    35  	}, nil
    36  }
    37  
    38  type checker struct {
    39  	domain    domain.ServiceServer
    40  	user      user.ServiceServer
    41  	session   session.ServiceServer
    42  	cache     cache.Cache
    43  	ip2Regoin ip2region.Service
    44  	log       logger.Logger
    45  }
    46  
    47  func (c *checker) MaxFailedRetryCheck(ctx context.Context, req *token.IssueTokenRequest) error {
    48  	ss := c.getOrDefaultSecuritySettingWithUser(ctx, req.Username)
    49  	if !ss.LoginSecurity.RetryLock {
    50  		c.log.Debugf("retry lock check disabled, don't check")
    51  		return nil
    52  	}
    53  	c.log.Debugf("max failed retry lock check enabled, checking ...")
    54  
    55  	var count uint32
    56  	err := c.cache.Get(req.AbnormalUserCheckKey(), &count)
    57  	if err != nil {
    58  		c.log.Errorf("get key %s from cache error, %s", req.AbnormalUserCheckKey(), err)
    59  	}
    60  
    61  	rc := ss.LoginSecurity.RetryLockConfig
    62  	c.log.Debugf("retry times: %d, retry limite: %d", count, rc.RetryLimite)
    63  	if count+1 >= rc.RetryLimite {
    64  		return fmt.Errorf("登录失败次数过多, 请%d分钟后重试", rc.LockedMinite)
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func (c *checker) UpdateFailedRetry(ctx context.Context, req *token.IssueTokenRequest) error {
    71  	ss := c.getOrDefaultSecuritySettingWithUser(ctx, req.Username)
    72  	if !ss.LoginSecurity.RetryLock {
    73  		c.log.Debugf("retry lock check disabled, don't check")
    74  		return nil
    75  	}
    76  
    77  	c.log.Debugf("update failed retry count, check key: %s", req.AbnormalUserCheckKey())
    78  
    79  	var count int
    80  	if err := c.cache.Get(req.AbnormalUserCheckKey(), &count); err == nil {
    81  		// 之前已经登陆失败过
    82  		err := c.cache.Put(req.AbnormalUserCheckKey(), count+1)
    83  		if err != nil {
    84  			c.log.Errorf("set key %s to cache error, %s", req.AbnormalUserCheckKey())
    85  		}
    86  	} else {
    87  		// 首次登陆失败
    88  		err := c.cache.PutWithTTL(
    89  			req.AbnormalUserCheckKey(),
    90  			count+1,
    91  			ss.LoginSecurity.RetryLockConfig.LockedMiniteDuration(),
    92  		)
    93  		if err != nil {
    94  			c.log.Errorf("set key %s to cache error, %s", req.AbnormalUserCheckKey())
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  func (c *checker) OtherPlaceLoggedInChecK(ctx context.Context, tk *token.Token) error {
   101  	ss := c.getOrDefaultSecuritySettingWithDomain(ctx, tk.Account, tk.Domain)
   102  	if !ss.LoginSecurity.ExceptionLock {
   103  		c.log.Debugf("exception check disabled, don't check")
   104  		return nil
   105  	}
   106  
   107  	if !ss.LoginSecurity.ExceptionLockConfig.OtherPlaceLogin {
   108  		c.log.Debugf("other place login check disabled, don't check")
   109  		return nil
   110  	}
   111  
   112  	c.log.Debugf("other place login check enabled, checking ...")
   113  
   114  	// 查询当前登陆IP地域
   115  	rip := tk.GetRemoteIP()
   116  	c.log.Debugf("query remote ip: %s location ...", rip)
   117  	login, err := c.ip2Regoin.LookupIP(rip)
   118  	if err != nil {
   119  		c.log.Errorf("lookup ip %s error, %s, skip OtherPlaceLoggedInChecK", rip, err)
   120  		return nil
   121  	}
   122  
   123  	// 查询出用户上次登陆的地域
   124  	queryReq := session.NewQueryUserLastSessionRequest(tk.Account)
   125  	us, err := c.session.QueryUserLastSession(ctx, queryReq)
   126  	if err != nil {
   127  		if exception.IsNotFoundError(err) {
   128  			c.log.Debugf("user %s last login session not found", tk.Account)
   129  			return nil
   130  		}
   131  
   132  		return err
   133  	}
   134  
   135  	if us.IpInfo == nil {
   136  		c.log.Debugf("last login session no ip info found, skip OtherPlaceLoggedInChecK")
   137  		return nil
   138  	}
   139  
   140  	// city为0 表示内网IP, 不错异地登录校验
   141  	if login.CityID == 0 || us.IpInfo.CityId == 0 {
   142  		c.log.Warnf("city id is 0, 内网IP skip OtherPlaceLoggedInChecK")
   143  		return nil
   144  	}
   145  
   146  	if us != nil {
   147  		c.log.Debugf("user last login city: %s (%d)", us.IpInfo.City, us.IpInfo.CityId)
   148  		if login.CityID != us.IpInfo.CityId {
   149  			return fmt.Errorf("异地登录, 请输入验证码后再次提交")
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (c *checker) NotLoginDaysChecK(ctx context.Context, tk *token.Token) error {
   156  	ss := c.getOrDefaultSecuritySettingWithUser(ctx, tk.Account)
   157  	if !ss.LoginSecurity.ExceptionLock {
   158  		c.log.Debugf("exception check disabled, don't check")
   159  		return nil
   160  	}
   161  	c.log.Debugf("not login days check enabled, checking ...")
   162  
   163  	// 查询出用户上次登陆的地域
   164  	queryReq := session.NewQueryUserLastSessionRequest(tk.Account)
   165  	us, err := c.session.QueryUserLastSession(ctx, queryReq)
   166  	if err != nil {
   167  		if exception.IsNotFoundError(err) {
   168  			c.log.Debugf("user %s last login session not found", tk.Account)
   169  			return nil
   170  		}
   171  
   172  		return err
   173  	}
   174  
   175  	if us != nil {
   176  		days := uint32(time.Now().Sub(time.Unix(us.LoginAt/1000, 0)).Hours() / 24)
   177  		c.log.Debugf("user %d days not login", days)
   178  		maxDays := ss.LoginSecurity.ExceptionLockConfig.NotLoginDays
   179  		if days > maxDays {
   180  			return fmt.Errorf("user not login days %d", days)
   181  		}
   182  		c.log.Debugf("not login days check passed, days: %d, max days: %d", days, maxDays)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func (c *checker) IPProtectCheck(ctx context.Context, req *token.IssueTokenRequest) error {
   189  	ss := c.getOrDefaultSecuritySettingWithUser(ctx, req.Username)
   190  	if !ss.LoginSecurity.IpLimite {
   191  		c.log.Debugf("ip limite check disabled, don't check")
   192  		return nil
   193  	}
   194  
   195  	c.log.Debugf("ip limite check enabled, checking ...")
   196  
   197  	return nil
   198  }
   199  
   200  func (c *checker) getOrDefaultSecuritySettingWithUser(ctx context.Context, account string) *domain.SecuritySetting {
   201  	ss := domain.NewDefaultSecuritySetting()
   202  	u, err := c.user.DescribeAccount(ctx, user.NewDescriptAccountRequestWithAccount(account))
   203  	if err != nil {
   204  		c.log.Errorf("get user account error, %s, use default setting to check", err)
   205  		return ss
   206  	}
   207  
   208  	return c.getOrDefaultSecuritySettingWithDomain(ctx, u.Account, u.Domain)
   209  }
   210  
   211  func (c *checker) getOrDefaultSecuritySettingWithDomain(ctx context.Context, account, domainName string) *domain.SecuritySetting {
   212  	ss := domain.NewDefaultSecuritySetting()
   213  	d, err := c.domain.DescribeDomain(ctx, domain.NewDescribeDomainRequestWithName(domainName))
   214  	if err != nil {
   215  		c.log.Errorf("get domain error, %s, use default setting to check", err)
   216  		return ss
   217  	}
   218  
   219  	return d.SecuritySetting
   220  }