Shiro权限管理框架(二)---Realm"加盐加密" | 掘金年度征文

550 阅读6分钟

「时光不负,创作不停,本文正在参加2021年终总结征文大赛

时光荏苒,在不努力就老啦~~~~

转眼已经毕业一年多两年了,回想起刚出来工作的那段时间,干劲十足,再后来变成了一个成天划水的老油条!

这里一年的工作中发现了自己很多的不足,就是技术菜啊!!!

所以开始回头深入研究一些经常用的技术

所以这两天刚注册的掘金在上面和大家分享一些自己总结的文章

一来在掘金上多分享多学习,锤练自己

二来也希望像我一样想改变自己,提升自己的朋友们多交流

今年快结束了~~~

希望明年能写更多的文章和大家分享,内容更丰富,排版更好看!

立个Flag 明年争取减肥成功,文章发表超过70篇


上一章:Shiro权限管理框架(一)---常用API与JavaSE环境下简单实现 - 掘金 (juejin.cn)

Shiro加盐加密

​ 加密的目的是从系统数据的安全考虑,如:用户的密码,如果我们不对其加密,那么所有用户的密码在数据库中都是明文,只要有权限查看数据库的都能够得知用户的密码,这是非常不安全的,同时一旦数据泄露,很多人多个账户都使用相同账户密码,涉及到财产或隐私的相关软件时,就很容易导致损失,所以,只要密码被写入磁盘,任何时候都不允许是明文, 以及对用户来说非常机密的数据,我们都应该想到使用加密技术;

如何实现项目中密码加密的功能:

1.添加用户的时候,对用户的密码进行加密

2.登录时,按照相同的算法对表单提交的密码进行加密然后再和数据库中的加密过的数据进行匹配

1.1 Shiro加密工具

  • 加密

在Shiro中实现了MD5 的算法,所以可以直接使用他它来对密码进行加密

@Test
public void testMD5() throws Exception{
    Md5Hash hash = new Md5Hash("123456");
  System.out.println(hash);
}
//输出结果:e10adc3949ba59abbe56e057f20f883e

如果MD5 加密的数据一样,那么无论在什么时候加密的结果都是一样的,所以,相对来说还是不够安全。image-20210618174240094.png

比如lanxw的密码是123456,加密后的密文为e10adc3949ba59abbe56e057f20f883e

在数据库中发现steflucy的密文和lanxw密文是一样的,那么也可以推断处理steflucy的密码也是123456


  • 加盐加密

但是我们可以对数据加“盐”。同样的数据加不同的“盐”之后就是千变万化的,因为我们不同的人加的“盐”都不一样。这样得到的结果相同率也就变低了。

盐一般要求是固定长度的字符串,且每个用户的盐不同。

可以选择用户的唯一的数据来作为盐(账号名,身份证等等),注意使用这些数据作为盐要求是不能改变的,假如登录账号名改变了,则再次加密时结果就对应不上了。

@Test
public void testMD5() throws Exception{
    Md5Hash hash = new Md5Hash("123456","45cb27");
    System.out.println(hash);
}
//输出结果:173eee408921a3509be87823f2dc307d

lanxwsteflucy的密码都为123456,加盐之后,他们的密文都不一样了. image-20210618175742666.png

Md5Hash() 构造方法中的第二个参数就是对加密数据添加的“盐”,加密之后的结果也和之前不一样了。如果还觉得不够安全,我们还可以通过加密次数来增加 MD5 加密的安全性。

@Test
public void testMD5() throws Exception{ 
    Md5Hash hash = new Md5Hash("123456","45cb27",3);
	System.out.println(hash);
}
//输出结果:8fd16c11f8b3fde5bde2c1eaacf6fb02

1.2 Realm加盐加密

我们在使用Shiro进行认证时,绝大多数情况都是使用Realm方式进行认证.我们要求密码具备一定的安全性,所以我们需要看使用Realm进行加盐加密.

  • 在数据库中我们的密码需要存储为密文,同时需要存储对应的盐.

新建User.java

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;//用户名
    private String password;//密码
    private String slat;//盐
}

新建 DataMapper.java

模拟数据库中加密的用户数据,角色和权限等

public class DataMapper {
    //用户集合
    private static Map<String, User> userData = new HashMap<String, User>();
    //角色集合
    private static Map<String, List<String>> roleData = new HashMap<String, List<String>>();
    //权限集合
    private static Map<String, List<String>> permissionData = new HashMap<String, List<String>>();
    static{
        //初始化用户数据
        //密码是666,加密3次
        User u1 = new User("liuxw","0e03fc180e8385a9c72f71bf427a79e0","45cb27");
        userData.put(u1.getUsername(),u1);
        userData.put(u1.getUsername(),u1);
        roleData.put(u1.getUsername(), Arrays.asList("seller"));
        permissionData.put(u1.getUsername(),
                Arrays.asList("customer:list","customer:save"));

        //密码是888,加密三次
        User u2 = new User("yl","1d3518c269000c9c506fac6b8276dcdc","c4eb61");
        userData.put(u2.getUsername(),u2);
        roleData.put(u2.getUsername(), Arrays.asList("seller","hr"));
        permissionData.put(u1.getUsername(),
                Arrays.asList("customer:list","customer:save","user:list","user:delete"));
    }
    //提供静态方法,模拟数据库返回数据
    public static User getUserByName(String username){
        return userData.get(username);
    }
    public static List<String> getRoleByName(String username){
        return roleData.get(username);
    }
    public static List<String> getPermissionByName(String username){
        return permissionData.get(username);
    }
}

新建一个UserRealm.java 继承AuthorizingRealm

public class UserRealm extends AuthorizingRealm {

    //授权
    /**提供授权信息*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //principalCollection.getPrimaryPrincipal()
        //其实就是在认证时放入SimpleAuthenticationInfo的第一个参数
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //根据登录用的名称查询到其拥有的所有角色的编码
        List<String> roleByName = DataMapper.getRoleByName(user.getUsername());
        //将用户拥有的角色添加到授权信息对象中,供Shiro权限校验时使用
        info.addRoles(roleByName);
        //根据登录用户的名称查询到其拥有的所有权限表达式
        List<String> permissionByName = DataMapper.getPermissionByName(user.getUsername());
        //将用户拥有的权限添加到授权信息对象中,供Shiro权限校验时使用
        info.addStringPermissions(permissionByName);

        return info;
    }

    //认证
    /**提供认证信息*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //从页面传入的账户
        String username = (String) token.getPrincipal();
        //模拟数据库中查询数据
        User user = DataMapper.getUserByName(username);
        if (user == null) {
            //如果没有查到就返回一个空
            return null;
        } else {
            //如果存在需要封装成AuthenticationInfo对象返回
            /**
             * user,身份对象,可以理解为在Web环境中登录成功后需要放入Session中的对象
             * user.getPassword()凭证(密码),需要和传入的凭证(密码)做比对
             * this.getName()当前 Realm 的名称,暂时无用,不需纠结
             * ByteSource.Util.bytes(user.getSlat()), 返回盐
             * */
            return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSlat()),this.getName());
        }
    }
}

测试类

 @Test
    public void testAuthorByRealmSlat(){
        //创建Shiro安全管理器,是Shiro的核心
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //创建自定义Realm对象
        UserRealm userRealm = new UserRealm();

        //创建凭证匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");
        //设置加密次数
        matcher.setHashIterations(3);
        //Realm管理凭证匹配器
        userRealm.setCredentialsMatcher(matcher);

        //安全管理器加载Realm对象
        securityManager.setRealm(userRealm);
        //把安全管理器注入到当前的环境中
        SecurityUtils.setSecurityManager(securityManager);
        //获取到subject主体对象
        Subject subject = SecurityUtils.getSubject();
        //创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("liuxw","666");

        subject.login(token);

        /*************************验证*****************************/

        //判断用户是否登录
        System.out.println("是否登录---->"+subject.isAuthenticated());

        //判断用户是否有某个角色
        System.out.println("是否有hr这个角色---->"+subject.hasRole("hr"));
        System.out.println("是否有seller这个角色---->"+subject.hasRole("seller"));


        //是否同时拥有多个角色
        System.out.println("是否同时拥有hr和seller----->"+Arrays.toString(subject.hasRoles(Arrays.asList("hr","seller"))));
        System.out.println("是否同时拥有hr和seller----->"+subject.hasAllRoles(Arrays.asList("hr","seller")));

        //判断用户是否有某个权限
        System.out.println("是否有用户删除权限------>"+subject.isPermitted("user:delete"));

    }

运行结果: image.png


后记:

​ 这一片分享和上一篇Shiro权限管理框架(一)---常用API与JavaSE环境下简单实现 是有很大关联的,有不明白的地方的,可以看上一篇文章,下一章我们将Shiro集成Spring