美文网首页
shiro单Realm实现多种登陆方式的扩展与实现

shiro单Realm实现多种登陆方式的扩展与实现

作者: 爱余星痕 | 来源:发表于2018-09-24 21:34 被阅读0次

    最近考虑给自己的平台增加新的登陆方式,上网查了一下相关的资料
    .我用的权限平台为shiro,如果要实现,需要实现多个Realm,我个人觉得这种方法有点麻烦,每增加一种登陆方式,都要实现Realm,就希望有一些简单的办法.
    整理需求如下:

    • 支持普通的用户密码验证
    • 密码验证可以让用户自由扩展,不一定是md5
    • 支持用户免密码验证
    • 新增登陆方式时,如需要新增手机号登陆,最少改动原有的代码

    经思考,实现如下:
    1.先实现自定义对象UsernamePasswordToken

        
    @Data
    public class UserNameLoginToken extends UsernamePasswordToken implements Serializable {
    
        /**
         * 登陆类型
         */
        private String loginType;
    
        public UserNameLoginToken() {
            super();
        }
    
        public UserNameLoginToken(final String username, final String password) {
            super(username, password);
        }
    
        /**
         *是否需要密码校验
         */
        private boolean requriedPassword;
    
    
        public static UserNameLoginToken buildNoPassword(String username, String loginType) {
            UserNameLoginToken userNameLoginToken = new UserNameLoginToken();
            userNameLoginToken.setUsername(username);
            userNameLoginToken.setLoginType(loginType);
            userNameLoginToken.setRequriedPassword(false);
            return userNameLoginToken;
        }
    
        public static UserNameLoginToken buildPassword(String username, String password, String loginType) {
            UserNameLoginToken userNameLoginToken = new UserNameLoginToken(username, password);
            userNameLoginToken.setLoginType(loginType);
            userNameLoginToken.setRequriedPassword(true);
            return userNameLoginToken;
        }
    
    
    }
    
    
    1. 整理登陆方式需要实现的接口类
    public interface ILoginService {
    
    
        /**
         * 通过token查找用户的信息
         * 可以是通过登陆名,也可以通过手机号,关键是UserNameLoginToken的构造成生成
         * @param userNameLoginToken
         * @return
         */
        UserInfoDto loadUserByToken(UserNameLoginToken userNameLoginToken);
        /**
         * 判断是否是该登陆类型的实现类
         * @param loginType
         * @return
         */
    
        boolean isSupportLogin(String loginType);
    
        /**
         * 判断登陆密码是否正确
         * @param userNameLoginToken
         * @param info 可以允许为空,如果密码为空,可以在info里查找后台密码
         * @param password 密码
         * @return
         */
        boolean isPasswordMatch(UserNameLoginToken userNameLoginToken, AuthenticationInfo info,Object  password);
    }
    
    

    所有登陆验证,都实现该接口ILoginService

    1. 自己实现一种简单的普通登陆验证
    
    @Service
    public class LoginCommonService implements ILoginService {
    
        public static final String LOGINCOMMON_LOGINTYPE = "md5";
    
        /**
         * 组织服务API
         */
        @Autowired
        private ISysOrgApiService sysOrgApiService;
    
        @Override
        public UserInfoDto loadUserByToken(UserNameLoginToken userNameLoginToken) {
            String username = (String) userNameLoginToken.getPrincipal();
            return sysOrgApiService.loadUserByUsername(username);
        }
    
        @Override
        public boolean isSupportLogin(String loginType) {
            return LOGINCOMMON_LOGINTYPE.equalsIgnoreCase(loginType);
        }
    
        @Override
        public boolean isPasswordMatch(UserNameLoginToken authcToken, AuthenticationInfo info, Object accountCredentials) {
    
            Object                tokenCredentials = MD5Util.getMD5String(authcToken.getUsername()
                    + String.valueOf(authcToken.getPassword()));
    
    
            return accountCredentials.equals(tokenCredentials+"");
        }
    }
    
    
    1. 根据登陆方式,获取需要的实现类
    public class LoginServiceUtil {
    
        public static ILoginService getLoginServcie(String loginType) {
            //获取登陆方式接口的所有实现类
            Map<String, ILoginService> loginServiceMap = SpringContextUtils.getApplicationContext().getBeansOfType(ILoginService.class);
            for (ILoginService value : loginServiceMap.values()) {
                if(value.isSupportLogin(loginType))return value;
            }
            return null;
        }
    }
    
    1. 改造Realm如下;
     /* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密是否正确。 */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
                throws AuthenticationException {
            UserNameLoginToken userNameLoginToken      = (UserNameLoginToken) authcToken;
            //根据登陆方式,获取相关人员对象,userNameLoginToken里的username可能是登陆名,手机号,邮箱,不确实,可扩展
            UserInfoDto userInfo = LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).loadUserByToken(userNameLoginToken);
    
            if (null == userInfo) {
                throw new AuthenticationException("用户不存在");
            }
    
            if(userNameLoginToken.isRequriedPassword()&&
                    !LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).isPasswordMatch(userNameLoginToken,null,userInfo.getPassword())){
                throw new IncorrectCredentialsException("密码错误");
            }
            SecurityUser securityUser = new SecurityUser();
    
            securityUser.setUserInfo(userInfo);
    
            // 匹配管理员角色
            Optional.ofNullable(userInfo.getOrgIds()).ifPresent(orgIds -> {
                    securityUser.setRoles(sysAuthApiService.getRoleIdsByOrgId(orgIds));
                    securityUser.setPermissionSet(sysAuthApiService.getPermissionIdsByOrgId(orgIds));
                });
    
            // 匹配管理员角色
            Optional.ofNullable(securityUser.getRoles()).ifPresent(roles -> {
                    boolean isAdmin = roles.stream().anyMatch(role -> SecurityConstants.SUPER_ADMIN.equals(role));
    
                    securityUser.setAdmin(isAdmin);
                });
    
            return new SimpleAuthenticationInfo(securityUser, securityUser.getUserInfo().getPassword(), getName());
        }
    
    
    1. 修改密码验证方式
    
    
    public class CredentialsMatcher extends SimpleCredentialsMatcher {
        private final static Logger LOGGER = LoggerFactory.getLogger(CredentialsMatcher.class);
    
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    
            UserNameLoginToken userNameLoginToken = (UserNameLoginToken) token;
            //如果是免密码登陆,直接返回
            if (!userNameLoginToken.isRequriedPassword()) {
                return true;
            }
    
            return LoginServiceUtil.getLoginServcie(userNameLoginToken.getLoginType()).isPasswordMatch(userNameLoginToken, info, getCredentials(info));
        }
    }
    
    
    1. 调整登陆接口
     @ApiOperation(value = "登陆")
        @RequestMapping(
                value = "/login",
                method = RequestMethod.POST
        )
        public SuccessResponseData login(@RequestBody LoginUserVo loginUserVo) {
            SuccessResponseData successResponse = new SuccessResponseData();
            UserNameLoginToken token = UserNameLoginToken.buildNoPassword(loginUserVo.getUsername(),"nopassword");
           // UserNameLoginToken token = UserNameLoginToken.buildPassword(loginUserVo.getUsername(),loginUserVo.getPassword(),"md5");
    
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
            successResponse.setData(subject.getSession().getId());
            successResponse.setMsg("登录成功");
            System.out.println(JSON.toJSONString(successResponse));
            return successResponse;
        }
    

    登陆时,根据需要的登陆方式构造UserNameLoginToken即可
    新增新的登陆方式,只需要做两步.
    1.实现接口类ILoginService
    2.调整登陆接口

    经测试,免密码登陆也是轻松就实现了

    相关文章

      网友评论

          本文标题:shiro单Realm实现多种登陆方式的扩展与实现

          本文链接:https://www.haomeiwen.com/subject/upphoftx.html