wiki草稿

整理2.0当前登录认证实现过程

过滤链

JwtAuthenticationTokenFilter -> LicenseExpireFilter -> 接口中的过滤器

JwtAuthenticationTokenFilter

首先从请求的header中获取token
如果token存在并且是以指定的前缀开头那么开始判断token是否正确

首先把token的前缀去掉,然后获得token中的Claims 然后从claims的subjiect获得username,如果当前用户名不为空并且安全上下文不为空,那么继续验证token。

调用loadUserByUsername方法判断用户名是否存在,是否被禁用,并且set用户对应的角色的菜单权限。

然后调用jwtTokenUtil.validateToken方法验证页面是否过期,验证用户名是否存在,验证token是否失效,如果不是系统用户还会调用所有自定义拦截器的tokenInterceptorHandler方法

如果验证都通过了那么就设置安全上下文和
request.setAttribute(“requestUserEntity”, securityUserEntity);
request.setAttribute(“requestUserAccount”, username);

如果上述过程抛出了异常 那么一律转发到/filter/login_auth_fail 然后返回给前端

登录接口

/auth/login

请求进入登录接口首先会调用

//根据dto中的用户名去mysql获取用户实体并且根据角色id得到菜单访问权限 (把 "菜单id_菜单权限" set成 Authorities)
 UserDetails userDetails = securityUserServiceImpl.loadUserByUsername(dto.getUsername());

该方法会对用户名进行判断 如果用户名不存在或者用户被禁用那么方法会抛出异常,如果用户名没有问题那么系统会根据当前用户的roleId查询菜单权限表 ,然后把对应的权限set到SecurityUserEntity 实体中,然后返回。

然后继续调用

//根据用户名和密码生成upToken
UsernamePasswordAuthenticationToken upToken = newUsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword());

根据用户名和密码生成upToken,然后调用方法获得自定义拦截器的所有实现类

Map<String, ICustomInterceptor> interceptorMap = applicationContext.getBeansOfType(ICustomInterceptor.class);

然后从mysql中获取一条安全策略,根据配置文件中的defaultSysUser来判断当前用户是否为admin 上面都是准备工作。

如果用户不是admin 那么就调用每一个ICustomInterceptor的实现类的beforeInterceptorHandler方法 挨个拦截!

FirstLoginInterceptorImpl 首次登陆拦截(after和token)

从安全策略中获取 是否开启首次登录拦截 如果开启并且用户没有过修改密码的记录那么就抛出异常10002。

IpRuleInterceptorImpl IP规则拦截(before和token)

首先根据userId返回ip规则实体列表(sys_ip_rule),然后根据HttpRequest获取当前请求的IP,然后从Reids查询是否有当前IP被lock的记录 如果被锁住就抛出10015,如果没有被锁住就看ip是否在ipAccess中(webcoreatd.sys_ip_rule.access_ip),如果不在允许范围内就抛出10008;

PasswrodExpiredInterceptorImpl 密码是否超期拦截(before和token)

首先获得安全策略中的密码期限然后根据最后一次修改密码的时间或者创建用户的时间 和密码期限来判断密码是否过期,如果密码过期抛出10009;

TimeRuleInterceptorImpl 时间规则拦截(before和token)

进入方法会先判断一次当前用户是否被锁住,然后根据用户id获取对应的时间拦截规则(sys_time_rule)(周几,时间),然后判断当前时间是否允许当前用户登录,如果不符合规则那么抛出10007;

TwofactorInterceptorImpl 双因子拦截(after)

首先从安全策略中获取是否开启双因子拦截或者开启了邮箱验证拦截或者短信验证拦截,如果双因子拦截失败抛出10006,如果只开了邮箱验证拦截并且验证失败抛出10021,如果只开启了短信验证拦截并且拦截失败那么抛出10022;

如果上述拦截都通过了那么就调用Springsecurit的安全验证方法 传入upToken返回一个authentication , 然后根据authentication设置安全上下文 , 如果账户失效(根据报错信息)那么抛出10010,如果认证失败抛出10010。

认证成功后会调用auth方法 验证密码是否匹配,如果密码不正确 返回10005

接下来根据passwordTimeLimit和lastModifyPasswordTime或createTime设置密码剩余时间

如果如果上述过程出现了异常10005 那么调用loginFail方法根据安全策略计算剩余输入密码机会 并抛出10005密码错误,剩余n次机会。

如果上面的过程都没有问题那就说明登录成功了,调用loginSuccess方法记录相关属性,(token创建时间(mysql)和页面过期时间(redis))然后根据当前时间和用户名生成一个JWT的token,并且返回token,登陆成功。

登出接口

/user/loginOut

从request中获取实体信息

request.getAttribute("requestUserEntity");

然后通过操作使token无效

 //该操作会使token无效
        userEntity.setIssueTokenTime(LocalDateTime.now().plusDays(1));
        securityUserMapper.updateById(userEntity);
        
///因为这个方法
        //验证校验是否失效
        jwtValidateToken.isExpired(claims, securityUserEntity.getIssueTokenTime());
       

这个方法也使用了这个操作 可能不对?在这里插入图片描述

涉及到的mysql表以及redis key

MYSQL表:

sys_ip_rule

id主键
user_id用户id
access_ip可访问ip

sys_login_fail

id主键
fail_type登录失败错误类别: 1,用户名、密码错误 2、双因子验证码不对 3、
fail_reason登录失败原因描述
fail_login_username登陆失败用户名
fail_login_password登陆失败用户的密码
fail_login_ip登陆失败用户的ip
fail_login_time登陆失败的时间

sys_menu

id
urlurl
menu_describe资源描述信息
menu_level资源的层级
path资源的完整路径
parent_id上级资源ID
is_view当前资源是否可以显示0表示可以显示,1表示不可以显示
order_num资源的顺序编号
op_id
op_value
icon
component
type
front_path

sys_password_record

id
user_id
password
change_time

sys_role

id
role_name
remark
create_time

sys_role_menu

id
role_id
menu_id
menu_authority菜单权限,0: 仅查看 1:可编辑

sys_security_policy

id
is_enable_first_login_change_password是否开启,初次登录修改密码策略(1:开启 0:关闭)
is_enable_strong_password是否开启,强密码策略(1:开启 0:关闭)
password_time_limit密码使用时间期限策略,单位: 天 (0表示无期限)
historical_password_check_count新密码不能与近期的多少次历史密码重复。0为不进行校验
page_timeout页面超时时间,单位:分 (0:不超期)
is_enable_two_factor是否开启双因子认证(0:不开启 1:开启)
is_enalbe_lock_fail_ip
cheak_time
login_fail_times
lock_time
password_length
have_number
have_lowercase
have_capital
have_special
is_enable_mail_factor
is_enable_sms_factor

sys_time_rule

id
user_id
access_week可访问星期几
access_time_begin可访问时间段起始,格式 HHmmss
access_time_end可访问时间段结束,格式 HHmmss

sys_user

id主键
role_id主键
username用户名,登录系统帐号
password密码
realname用户真实名称
email电子邮箱
tel手机号码
jobnumber工号
status用户状态: 1启用 0禁用,默认1
expired_time超期时间戳
issue_token_time最近一次登录的token发放时间
last_modify_password_time最近一次密码修改时间
create_user_id创建者id
create_time用户创建时间
branch_id分支id

redis key

  • 登录成功后在redis中设置当前用户的页面过期时间 username+“pageTimeOut”,issue
 redisTemplate.opsForValue().set(userDetails.getUsername() + "pageTimeOut", issue);
  • 发送短信并将验证码存在redis
 redisTemplate.opsForValue().set("login" + tel, code);

在这里插入图片描述

  • 密码输错次数过多 锁住该用户
redisTemplate.opsForValue().set("lock" + dto.getUsername(), dto.getUsername(), lockTime.intValue(), TimeUnit.MINUTES);
  • 使用redis存储邮箱验证码
 redisTemplate.opsForValue().set("login" + email,code,5, TimeUnit.MINUTES);
  • 邮箱短信双因子验证
redisTemplate.opsForValue().set("login" + tel, code);

优化方案:

取消不带token可以直接访问

获取请求路径 如果是登录那么直接放过 uri : “/auth/login”
如果是登出 setSecurityUserEntity之后放过 uri : “/user/loginOut”
如果是swagger 放过 url: “http://localhost:15000/*”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值