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

最近考虑给自己的平台增加新的登陆方式,上网查了一下相关的资料 .我用的权限平台为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.调整登陆接口

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿杜的世界

Java Web技术经验总结(十七)

882
来自专栏Spring相关

oauth2.0实现sso单点登录的方式和相关代码

百科:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的...

6302
来自专栏偏前端工程师的驿站

Java魔法堂:URI、URL(含URL Protocol Handler)和URN

一、前言                                 过去一直搞不清什么是URI什么是URL,现在是时候好好弄清楚它们了!本文作为学习笔记,...

2365
来自专栏zhisheng

渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)

上篇文章写完了 ES 流程启动的一部分,main 方法都入口,以及创建 Elasticsearch 运行的必须环境以及相关配置,接着就是创建该环境的节点了。

1573
来自专栏A周立SpringCloud

Spring Boot、Dubbo项目Mock测试踩坑与总结

本文是对Spring Boot、Dubbo项目进行Mock测试的总结与踩坑实录。 搜索了一圈,居然没发现类似的文章,莫非用Dubbo的朋友们都不Mock测试,或...

7297
来自专栏程序猿DD

从零开始的Spring Security Oauth2(二)

本文开始从源码的层面,讲解一些spring Security Oauth2的认证流程。本文较长,适合在空余时间段观看。且涉及了较多的源码,非关键性代码以…代替。...

2806
来自专栏开发技术

shiro源码篇 - shiro的session共享,你值得拥有

    老师对小明说:"乳就是小的意思,比如乳猪就是小猪,乳名就是小名,请你用乳字造个句"     小明:"我家很穷,只能住在40平米的乳房"     老师:"...

6314
来自专栏大数据平台TBDS

Flume-Hbase-Sink针对不同版本flume与HBase的适配研究与经验总结

导语:本文细致而全面地讲解使用flume输出数据到HBase的三种不同 Flume-Hbase-Sink 之间的差异性,以及技术细节。并且透彻而全面地总结了不同...

2.1K12
来自专栏xingoo, 一个梦想做发明家的程序员

日志那点事儿——slf4j源码剖析

前言: 说到日志,大多人都没空去研究,顶多知道用logger.info或者warn打打消息。那么commons-logging,slf4j,logback...

2685
来自专栏用户2442861的专栏

Python标准模块logging

http://blog.csdn.net/fxjtoday/article/details/6307285

951

扫码关注云+社区

领取腾讯云代金券