首页 > 其他分享 >多类型登录-单认证口径集成认证方案-代码层实现

多类型登录-单认证口径集成认证方案-代码层实现

时间:2023-01-13 17:34:10浏览次数:46  
标签:集成 PrincipalInfo String 登录 认证 token protected public principal

1、背景

公司内部的OA系统,慢慢的迭代升级,最终转型成为【综合业务系统】。很多大型系统最终都将走向类似于【企业微信】这样的大型系统发展。

前期:系统只存在,如:【用户(人)】这样的使用者进行登录和权限认证过程。慢慢的,【xx应用】、【xx设备】、【xx其他使用类型】都需要用到系统功能,比如【xx应用】需要使用【内部合同模块】,【xx设备】需要使用【内部门锁模块】等等情况。

问题:这时候如果只靠模拟用户登录,很多很多地方都接入了,都用了某个用户登录信息,那到底是不是这个人操作等问题就出现了。特别是:单点登录这种常用功能,就会出现用户挤兑现象。

总结:从管理学角度,其实谁【使用者】用了什么东西,最好严格区分,如果出现问题,也方便排查,责任划分也更方便。从设计学角度,这样的设计也算是数据分离,对于维护是有好处的。

2、一期的简单改造

其他系统,如:【xx应用】也需要接入系统,也需要登录和授权。那么,可以尝试在:【用户认证+权限检测】处进行统一分发处理,使用策略模式进行改造。

多类型登录-单认证口径集成认证方案-代码层实现_设计架构


3、代码实现

首先做个BaseToken类,用于继承Token,方便给各个实现扩展

public class BaseToken implements Serializable {

protected static final long serialVersionUID = 1L;

// token类型
protected String tokenType = Const.DEFAUTL_TOKEN_TYPE;

// 登录对象ID
protected String principalId;

//用户ID
protected Integer userId;

//token
protected String token;

//过期时间
protected Date expireTime;

//更新时间
protected Date updateTime;

// 过期时间
protected Integer expire;

}

再做一个PrincipalInfo,用户继承使用者信息,如:userInfo

public class PrincipalInfo {
protected static final long serialVersionUID = 1L;

/**
* 使用类型
*/
protected String tokenType = "user_device_token";

/**
* 登录对象ID
*/
protected String principalId = "";

/**
* 用户ID
*/
protected Integer userId = 0;
/**
* 用户名(账户)
*/
protected String userName = "";
/**
* 昵称
*/
protected String nickName = "";
/**
* 用户类型 1-系统用户,2-客户
*/
protected Integer userType;
/**
* 手机号码
*/
protected String mobile;
/**
* 状态 0-禁用 1-正常
*/
protected Integer status;
/**
* 是否锁定:1-是 0-否
*/
protected Integer isLock;

protected String avatar;
}

中转工具统一封装一下,这里拆分为两个类:一个是token工厂类,用于产生token;另一个是认证类,用于用户和权限认证。

@Slf4j
@Component
public class TokenServiceUtil {

@Resource
ApplicationContext applicationContext;

@Resource
RedisUtil redisUtil;

@Component
public class Factory{

public BaseToken createToken(String beanName, JSONObject param){
try{
TokenConfigBaseService util = (TokenConfigBaseService) applicationContext.getBean(beanName);
if(null == util){
throw new KgoaException(Result.TOKEN_UTIL_NOT_EXIST);
}
// 创建token
return util.createToken(beanName, param);
}catch (NoSuchBeanDefinitionException e){
throw new KgoaException(Result.TOKEN_UTIL_NOT_EXIST);
}
}

}

@Component
public class Auth{

/**
* 鉴权token
*/
public PrincipalInfo authPrincipal(String token){
try{
// 检查token
BaseToken tokenEntity = redisUtil.getCacheObject(RedisConst.TOKEN_PREFIX + token);
if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
throw new KgoaException(Result.TOKEN_INVALID);
}

// 获取响应工具
TokenConfigBaseService util = (TokenConfigBaseService) applicationContext.getBean(tokenEntity.getTokenType());
if(null == util){
throw new KgoaException(Result.TOKEN_UTIL_NOT_EXIST);
}

// 鉴权token
return util.authPrincipal(token);
}catch (NoSuchBeanDefinitionException e){
throw new KgoaException(Result.TOKEN_UTIL_NOT_EXIST);
}
}

/**
* 获取授权
*/
public Set<String> getPermissions(PrincipalInfo principal){
// 获取响应工具
TokenConfigBaseService util = (TokenConfigBaseService) applicationContext.getBean(principal.getTokenType());
if(null == util){
throw new KgoaException(Result.TOKEN_UTIL_NOT_EXIST);
}

return util.getPermissions(principal);
}

}
}

然后就是,抽象接口,用于作为通用实现的上层处理。

public interface TokenConfigBaseService {

/**
* 创建token
*/
BaseToken createToken(String beanName, JSONObject param);

/**
* 检查主体
*/
PrincipalInfo authPrincipal(String token);

/**
* 获取权限
* @return
*/
Set<String> getPermissions(PrincipalInfo principal);
}

通用实现​

@Service("user_device_token")
public class UserDeviceImpl implements TokenConfigBaseService {

@Autowired
private UserTokenService userTokenService;

@Autowired
private ShiroService shiroService;

/**
* 创建 token
*/
@Override
public BaseToken createToken(String beanName, JSONObject param) {
Integer userId = param.getInt("user_id");
LoginEndEnum loginEnd = LoginEndEnum.getLoginEndByCode(param.getInt("login_end"));
return userTokenService.createToken(userId, loginEnd);
}

/**
* 健全token
*/
@Override
public PrincipalInfo authPrincipal(String token) {
//查询系统用户信息
R res = shiroService.checkToken(token);
//检查用户异常状态
new GetAuthUserInfo().getAuthUserInfo(res);

UserInfo userInfo = (UserInfo) res.getData();
return userInfo;
}

/**
* 获取权限
*/
@Override
public Set<String> getPermissions(PrincipalInfo principal) {
Integer userId = principal.getUserId();

//用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);
return permsSet;
}

以上就是基础组件的代码,整体设计追求高耦合,需要将系统强关联起来,这样如果有一环出错,也能够顺利排查错误。

接下来就是权限认证的时候了,采用了springboot+shiro,贴出来AuthorizingRealm的代码,分别是:用户认证、权限认证

@Slf4j
@Component
public class OAuth2Realm extends AuthorizingRealm {

@Resource
private TokenServiceUtil.Auth auth;

@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}

/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
PrincipalInfo principal = (PrincipalInfo) principals.getPrimaryPrincipal();
if (null == principal) {
throw new KgoaException(Result.PRINCIPALS_NOT_EXIST);
}

//用户权限列表
Set<String> permsSet = auth.getPermissions(principal);

info.setStringPermissions(permsSet);
return info;
}

/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();

PrincipalInfo principal = auth.authPrincipal(accessToken);

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, accessToken, getName());
return info;
}
}

4、总结

上面的这种简单设计模式,可能还会存在隐藏的bug,比如说:多模块或者微服务架构下,其他模块直接调用

UserInfo user = (UserInfo) SecurityUtils.getSubject().getPrincipal();

直接获取用户信息,会出现和父亲类:PrincipalInfo,冲突的情况,或者当前方法用到了用户信息,APP登录却没有用户这种情况。

所以需要在详细优化这部分功能。

标签:集成,PrincipalInfo,String,登录,认证,token,protected,public,principal
From: https://blog.51cto.com/u_15595167/6006355

相关文章

  • 启动、登录、注销时执行Linux脚本
    问题启动Linux系统并登录的过程中到底发生了什么事情,按下开机键或启动一个虚拟机,你就启动了一系列事件,之后会进入到一个功能完备的系统中,当你注销或者关机时,也是这样。......
  • shiro登录认证过程讲解
    先粘出登录的代码​@RequestMapping(value="/submitLogin",method=RequestMethod.POST)@ResponseBodypublicMap<String,Object>submitLogin(Stringusername,String......
  • springboot集成swagger3
     1pom.xml增加  <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependen......
  • Angular集成bpmn.js的基础实现及扩展(一)
    一、bpmn的基本认识bpmn.js是一个BPMN2.0渲染工具包和web建模器,使得画流程图的功能在前端来完成。bpmn画图具有哪些内容?二、使用npm安装bpmn.jsnpminstall--sav......
  • 带绳窗帘出亚马逊做什么认证呢?
    美国消费品安全委员会于2022年11月2日通过了一项新的联邦安全标准。该标准适用于定制窗帘的操作绳,目的是降低带绳窗帘导致儿童窒息死亡和严重危及生命的伤害的风险。委员会......
  • 小程序用户登录架构设计
      1.背景上一篇文章《小程序静默登录方案设计》提到过,小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。即......
  • 如何实现小程序静默登录?
      .背景首先谈谈在小程序的开发中,如何借助微信的能力标识一个用户?微信官方提供了两种标识:OpenId是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标......
  • Telegram 部分地区支持邮箱登录
    Telegram部分地区支持邮箱登录1.注册时要求输入邮箱验证2.再次登录时Telegram会向绑定邮箱发送验证码邮件3.输入邮箱验证码之后可登录Telegram(没有收到已登录设备的......
  • 性能测试|电商业务性能测试(二): Jmeter 参数化功能实现注册登录的数据驱动
    1.前置条件此例使用的是GitHub上一个开源的电商项目mall,需要的可以去GitHub上下载部署,有详细的部署教程:GitHub地址:​​https://github.com/macrozheng/mall​​部署教......
  • 关键错误 你的“开始”菜单出现了问题。我们将尝试在你下一次登录时修复它。
    今天觉的users目录太大了,打算迁移到另外一块固态上。移完文件就报了这个错。搜索到了一个方法。进到目录C:\Windows\System32\WindowsPowerShell\v1.0\然后右键power......