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

简介: 最近考虑给自己的平台增加新的登陆方式,上网查了一下相关的资料.我用的权限平台为shiro,如果要实现,需要实现多个Realm,我个人觉得这种方法有点麻烦,每增加一种登陆方式,都要实现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.调整登陆接口

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

相关文章
|
6月前
|
数据库 数据安全/隐私保护
Shiro【自定义Realm 、多Realm认证 、多Realm认证策略、异常处理】(四)-全面详解(学习总结---从入门到深化)
Shiro【自定义Realm 、多Realm认证 、多Realm认证策略、异常处理】(四)-全面详解(学习总结---从入门到深化)
124 1
|
6月前
|
数据库 数据安全/隐私保护
Shiro【自定义Realm 、多Realm认证 、多Realm认证策略、异常处理】(二)-全面详解(学习总结---从入门到深化)
Shiro【自定义Realm 、多Realm认证 、多Realm认证策略、异常处理】(二)-全面详解(学习总结---从入门到深化)
352 0
|
12月前
Shrio配置多个Realm、SecurityManager认证策略
Shrio配置多个Realm、SecurityManager认证策略
135 0
|
Java 数据库 数据安全/隐私保护
用shiro框架实现注册登陆,让你快速理解shiro用法
用shiro框架实现注册登陆,让你快速理解shiro用法
493 0
用shiro框架实现注册登陆,让你快速理解shiro用法
|
存储 缓存 安全
2021年你还不会Shiro?----2.Shiro实现登录功能(身份认证实践)
上一篇介绍了Shiro的架构,我们可以发现Shiro核心的东西并不多,我们花个几分钟就可以把Shiro的机构记清楚,其中Security Manager就是Shiro的核心,他包含了身份认证器Authenticator、授权器Authorizer、Session管理Session Manager、缓存管理Cache Manager。这一篇我们就介绍下Shiro的身份认证的过程,也就是我们说的用户登录。
151 0
2021年你还不会Shiro?----2.Shiro实现登录功能(身份认证实践)
|
存储 缓存 前端开发
2021年你还不会Shiro?----8.使用Shiro实现权限管理(前后端)
这是一个系列的文章,这是第八篇,如果只是看了这一篇或者对于Shiro没有基础的人,看到这一篇可能并不会有多大收益。前面几篇文章已经介绍了Shiro+JSP+SpringBoot+Mybatis+mysql的整合,并实现了使用MD5+盐+hash散列的方式对密码进行加密的注册登录功能。这篇是基于之前的文章进行写作的,下面就要说下登录完成后怎么实现授权操作。也就是怎么使用Shiro实现权限管理,前后端的授权是分开的,准确的说是没有关系的,所以这里也是对前后端的授权操作分开讲解。
363 0
2021年你还不会Shiro?----8.使用Shiro实现权限管理(前后端)
|
缓存 安全 Apache
2021年你还不会Shiro?----3.分析身份认证源码实现自定义Realm
我们已经知道无论我们是认证还是授权,数据的获取都是来源于Realm,Realm就相当于我们的datasource,在上一篇中我们使用的是用IniRealm来加载我们的配置文件shiro.ini,同时我们也说了ini只是临时解决方案,在实际的开发中是不可能把用户信息和权限信息放在ini文件中的,都是来源于数据库,那么系统提供的IniRealm就不能满足我们的需要了,我们就需要自定义Realm来实现真正的场景,事实上ini文件也只是apache为我们提供学习使用的策略,下面我们就来看下怎么自己定义一个Realm。
113 0
2021年你还不会Shiro?----3.分析身份认证源码实现自定义Realm
|
安全 数据安全/隐私保护
【Shiro】4、Shiro实现记住登录功能
用户每次在登录系统时需要重新输入账户、密码、验证码等信息,非常麻烦,于是要求加一个记住登录的功能,这对于 Shiro 来说是非常简单,下面就让我们一起来实现记住登录功能
177 0
【Shiro】4、Shiro实现记住登录功能
|
缓存 前端开发 程序员
Shiro实现多realm方案
前后端分离的背景下,在认证的实现中主要是两方面的内容,一个是用户登录获取到token,二是从请求头中拿到token并检验token的有效性和设置缓存。
Shiro实现多realm方案
|
数据库 数据安全/隐私保护
【Shiro】3、Shiro实现自定义密码验证规则
我们在使用 Shiro 实现登录的时候,我们只需要将账号、密码,Shiro 会自动判断账户、密码是否正确,那么 Shiro 怎么会知道我们的密码加密规则呢?所以我们需要自定义密码的加密规则
227 0