栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 数据挖掘与分析

Shiro+Springboot实现数据库用户密码为空值也能验证登录

Shiro+Springboot实现数据库用户密码为空值也能验证登录

场景

一般用户是有密码的,有时候为了简便所以就给一些用户没设置密码了,导致数据库中他们那一批为null值,但此时我们又需要用Shiro验证登录,

思路

就是判断用户输入密码或者没输入密码用啥验证方式,如果没输入密码过来,我们就伪造一个假密码登录成功,实际上数据库并不存在那个假密码。

前期准备工作

首先你得先引入shiro的依赖,

 
     org.apache.shiro
     shiro-spring
     1.5.3
 
控制层

思路大部分都写注解里了,核心步骤就是subject.login(…)然后触发我们下面自定义写的验证类CustomRealm,然后确保验证无误

 @RequestMapping("/login")
    public Result login(@RequestBody User user) {
        Map obj = new HashMap<>();
        Boolean success;
        String returnCode;
        String message = "";
        if (user.getPassword().isEmpty()){   //判断用户是否有无输入密码
            User userByName = userService.getUserByName(user.getUsername());  //若没输入密码,则去数据库查询该用户是否存在
            if (org.springframework.util.StringUtils.isEmpty(userByName)){    //判断是否是用户名输错,密码没输入的情况
                success = Constant.SUCCESS_FALSE;
                returnCode = Constant.STATUS_CODE_500;
                message = "账号或密码错误!";
                return Result.build(success, returnCode, message);
            }else{
                if (StringUtils.isEmpty(userByName.getPassword())){         //判断该用户是否是真的数据库里面没密码还是有密码
                    // 进行免密登录
                    Subject subject = SecurityUtils.getSubject();
                    String password = "无需密码直接登录";
                 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(), password);
                //此处会调用我们自己写的验证类里面的doGetAuthenticationInfo方法进行判断是否校验成功   
                    subject.login(usernamePasswordToken ); 
                    success = Constant.SUCCESS_TRUE;
                    returnCode = Constant.STATUS_CODE_200;
                    message = "登录成功";
                    //登录成功---可以存一些基本信息,这里我省略我的一些存入基本信息的操作,例如存入的token
                    return Result.build(success, returnCode, obj, message);
                }else{
                        success = Constant.SUCCESS_FALSE;
                        returnCode = Constant.STATUS_CODE_500;
                        message = "账号或密码错误!";
                        return Result.build(success, returnCode, message);
                }
            }
        }else{
            //添加用户认证信息
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    user.getUsername(),
                    user.getPassword()
            );
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
                success = Constant.SUCCESS_TRUE;
                returnCode = Constant.STATUS_CODE_200;
                message = "登录成功";
            } catch (AuthenticationException e) {
                // e.printStackTrace();
                success = Constant.SUCCESS_FALSE;
                returnCode = Constant.STATUS_CODE_500;
                message = "账号或密码错误!";
                return Result.build(success, returnCode, message);
            } catch (AuthorizationException e) {
                success = Constant.SUCCESS_FALSE;
                returnCode = Constant.STATUS_CODE_500;
                //e.printStackTrace();
                message = "没有权限";
            }
            //登录成功--可以存一些基本信息,这里我省略我的一些存入基本信息的操作,例如存入的token
            return Result.build(success, returnCode, obj, message);
        }

    }

准备创建一个ShiroConfig配置类

//这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!

    @Bean
    public CredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(MD5.hashAlgorithmName);// 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(MD5.hashIterations);// 散列的次数,比如散列两次,相当于
        // md5(md5(""));
        return hashedCredentialsMatcher;
    }
    
    public DefaultHeaderSessionManager SessionManager() {
        DefaultHeaderSessionManager sessionManager = new DefaultHeaderSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setGlobalSessionTimeout(1000*60*60*24);//单位:毫秒 秒 分 小时 天
        return sessionManager;
    }
     //将自己的验证方式加入容器************此处比较重要
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }
     //权限管理,配置主要是Realm的管理认证
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(SessionManager());
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //对所有用户认证
//        map.put("/**", "authc");
        map.put("/**", "anon");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
//这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!    

准备创建自己的Shiro登录验证方式,创建一个CustomRealm类,

public class CustomRealm extends AuthorizingRealm {

    @Reference
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户信息(登录验证成功时存入的)
        User user = (User) principalCollection.getPrimaryPrincipal();
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
        }
        for (Auth auth : user.getAuths()) {
            simpleAuthorizationInfo.addStringPermission(auth.getPath());
        }
        return simpleAuthorizationInfo;
    }

    //无密码登录的核心是这块**********************************************************************
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        if (authenticationToken.getPrincipal() == null) {
            return null;
        }
        // 1. 把AuthenticationToken转换为UsernamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        // 2. 从UsernamePasswordToken中获取staffId
        String name = upToken.getUsername();
       // System.out.println("密码"+upToken.getPassword());
        //查看UsernamePasswordToken可知,getCredentials()方法的返回值是char []类型的,所以不能直接转化成string。
        char [] ch = (char[]) authenticationToken.getCredentials();
        //接收输入的密码
        String password = new String(ch);
        // 3. 若用户不存在,抛出UnknownAccountException异常
        User user= userService.getUserByName(name);
        if (user == null) {
            throw new UnknownAccountException("用户不存在!");
        }
        // 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
        // 判断是否是免密登录。
        // 在控制器里,正常的登录(绑定)逻辑是用户输入工号和密码,直接登录是直接把密码赋值为:无需密码直接登录
        if ("无需密码直接登录".equals(password)){
            //MD5 10次加密后
            String passwordMD5 = "3b34283253b91a7d7ad1713fafde85cc";
            String realmName = getName();
   SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, passwordMD5, null,realmName);
            return simpleAuthenticationInfo;
        }else {
            User userInfo = userService.getAuth(user.getUserId());
            ByteSource salt = ByteSource.Util.bytes(userInfo.getUserId());
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    userInfo,
                    user.getPassword(),
                    salt,
                    getName());
            return simpleAuthenticationInfo;
        }
    }
}

注意上面那个字符串passwordMD5 是用“无需密码直接登录”进行MD510次加密后的结果,推荐创一个工具类MD5,代码如下

public class MD5 {

    //加密方式
    public final static String hashAlgorithmName = "MD5";
    //加密次数
    public final static int hashIterations = 10;
    //带盐值加密
    public static final String md5(String password, String salt) {
        //盐:为了即使相同的密码不同的盐加密后的结果也不同
        ByteSource byteSalt = ByteSource.Util.bytes(salt);
        //密码
        Object source = password;
        SimpleHash result = new SimpleHash(hashAlgorithmName, source, byteSalt, hashIterations);
        return result.toString();
    }
    //单纯密码加密
    public static final String md52(String password) {
        //密码
        Object source = password;
        SimpleHash result = new SimpleHash(hashAlgorithmName, source, null, hashIterations);
        return result.toString();
    }

    public static void main(String[] args) {
        String pwd = "无需密码直接登录";//加密前密码
        String password = md52(pwd);
        System.out.println(password);
    }
}
总结

为了让大家能更看明白些,以上代码有所删减,如有不懂可以留言,看到即回

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/278589.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号