什么Shiro?

  1. Apache的强大灵活的开源安全框架
  2. 提供, 认证, 授权, 企业会话管理, 安全加密, 缓存管理

一般我们使用Shiro能够快捷方便的完成项目里的权限管理模块开发

Shiro与Spring Security

Apache ShiroSpring Security
简单,灵活,轻量级复杂,笨重,重量级
可脱离Spring不可脱离Spring
粒度较粗粒度更细

我个人更喜欢用Shiro, 主要因为它比较简单, 灵活, 有些没有的功能我们可以自己去拓展. 权限粒度较粗这一块, 因为我们基本上是基于资源去做权限控制. 如果我们要做数据权限的话, 一定会和我们业务代码耦合, 所以Spring Security的控制粒度更细也没有体现出来, 或者说没有体现的很明显. 而且Spring的官网它也是用的Shiro做安全管理.

Shiro整体架构

  • 首页最上面浅黄的部分可以理解为当前的----操作用户

Security Manager部分是我们Shiro的核心. 我们来看一下它的组件

  • Authenticator(认证器)管理我们的登陆, 登出
  • Authorizer(授权器) 赋予我们主体的权限
  • SessionManager(会话管理器)可以不在不借助任何web容器下使用Session
  • SessionDAO(会话操作)提供了Session的操作, 主要是有增删改查
  • CacheManager(缓存管理器)利用缓存管理器可以缓存我们的角色数据和权限数据
  • PluggableRealms(插接式连接器)Shiro获取认证信息, 权限数据, 角色数据, 连接数据库
  • Cryptography(加密器)可以使用它, 非常快捷便利的行进加密

用户提交请求到SecurityManager, SecurityManager调用认证器去做一个认证, 而认证器是通过插接式连接器, 从数据库中查询获取到认证信息. 授权器同样如此.

Shiro认证

  1. 创建SecurityManager对象, 构建SecurityManager的环境
  2. 主体提交认证(也就是上图中的最上面部分,操作用户)到SecurityManager进行认证
  3. SecurityManager调用Authenticator(认证器)进行认证, 进行认证的时候需要使用PluggableRealms(插接式连接器)获取认证数据, 然后再进行认证.

认证小demo(可以改一下不正确的角色认证试试看会有什么效果)

SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

    @Before
    public void addUser(){
        //添加一个用户
        simpleAccountRealm.addAccount("ljj","123");
    }

    @Test
    public void testAuthentication(){
        //1. 构建SecurityManager环境
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(simpleAccountRealm);//设置Realm环境

        //2. 主体提交认证请求
        SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
        Subject subject = SecurityUtils.getSubject();//获得主体
        UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
        subject.login(token);//进行验证

        //是否认证成功
        System.out.println(subject.isAuthenticated());

        //登出
        subject.logout();

        System.out.println(subject.isAuthenticated());
    }

Shiro授权

shiro授权和基本一致, 只不过是将Authenticator(认证器)更换成了Authorizer(授权器)

授权小demo (可以改一下不正确的角色权限试试看会有什么效果)

	@Before
    public void addUser(){
        //添加一个用户, 并赋予角色权限,可以有多个角色
        simpleAccountRealm.addAccount("ljj","123","admin","user");
    }

	@Test
    public void testAuthentication(){
        //1. 构建SecurityManager环境
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(simpleAccountRealm);//设置Realm环境

        //2. 主体提交认证请求
        SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
        Subject subject = SecurityUtils.getSubject();//获得主体
        UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
        subject.login(token);//进行验证

        //是否认证成功
        System.out.println(subject.isAuthenticated());

        //权限认证, 单个是checkRole, 多个才要加 s
        subject.checkRoles("admin","user");
    }

Shiro—IniRealm

首页我们得在resources文件夹下创建一个*.ini文件

内容如下

[users]    //认证信息, 角色权限
ljj=123,admin
[roles]   //角色权限限制
admin=user:delete,user:update

java代码

@Test
    public void testAuthentication(){
		//创建IniRealm对象
        IniRealm iniRealm = new IniRealm("classpath:user.ini");

        //1. 构建SecurityManager环境
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(iniRealm);//设置Realm环境
        //2. 主体提交认证请求
        SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
        Subject subject = SecurityUtils.getSubject();//获得主体
        UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
        subject.login(token);//进行验证

        //是否认证成功
        System.out.println(subject.isAuthenticated());

        subject.checkRole("admin");

        //判断是否具有对应操作的权限
        subject.checkPermission("user:update");
    }

大家可以尝试认证错误信息以及错误权限看看会发生什么情况

Shiro----jdbcRealm

感谢大家的关注, 关于连接数据库进行认证与授权的教程我将在Shiro实战教程中去编写, 点个赞支持一下作者.

Shiro—自定义

自定义Realm需要继承于AuthorizingRealm, 并且实现它的两个方法, 如下

	/**
     * 授权
     * @param principalCollection 授权信息
     * @return
     */
	@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

	/**
     * 认证
     * @param authenticationToken 认证信息
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }

如注释所描述一样, 在其方法中便可进行认证与授权了.

认证

public class CustomRealm extends AuthorizingRealm {

    Map<String, String> userMap = new HashMap<>();
    {
        userMap.put("ljj","123");
        //realmName可以随意取名
        super.setName("customRealm");
    }

    /**
     * 认证
     * @param authenticationToken 认证信息
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //1.从主体传过来的认证信息中,获取用户名
        String userName = (String) authenticationToken.getPrincipal();

        //2.通过用户名到数据库中获取凭证
        String password = getPasswordByUserName(userName);
        //如果不存在
        if (password == null) {
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                "ljj",password,"customRealm");
        return authenticationInfo;
    }

    /**
     * 模拟数据库查询凭证
     * 一般我们需要在这里连接数据进行验证, 由于我这只是一个小demo就直接写了一个map
     * 大家可以去连接数据库进行验证
     * @param userName
     * @return
     */
    private String getPasswordByUserName(String userName){
        return userMap.get(userName);
    }

测试类

	@Test
    public void testAuthentication(){

        CustomRealm customRealm = new CustomRealm();

        //1. 构建SecurityManager环境
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(customRealm);//配置环境
        //2. 主体提交认证请求
        SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
        Subject subject = SecurityUtils.getSubject();//获得主体
        UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
        subject.login(token);//进行验证

        //是否认证成功
        System.out.println(subject.isAuthenticated());

授权:

	/**
     * 授权
     *
     * @param principalCollection 授权信息
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        String userName = (String) principalCollection.getPrimaryPrincipal();
        //从数据库或者缓存中获取角色数据
        Set<String> roles = getRolesByUserName(userName);
        //从数据库或者缓存中获取权限数据
        Set<String> permissions = getPermissionsByUserName(userName);
        //创建SimpleAuthorizationInfo对象, 返回得到的数据
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //设置权限
        simpleAuthorizationInfo.setStringPermissions(permissions);
        //设置角色
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 模拟数据库查询权限
     * @param userName
     * @return
     */
    private Set<String> getPermissionsByUserName(String userName) {
        Set<String> sets = new HashSet<>();
        sets.add("user:delete");
        sets.add("user:add");
        return sets;
    }

    /**
     * 模拟数据库查询授权
     * 这里和getPasswordByUserName一样
     *
     * @param userName
     * @return
     */
    private Set<String> getRolesByUserName(String userName) {
        Set<String> sets = new HashSet<>();
        sets.add("admin");
        sets.add("user");
        return sets;
    }

测试类
在认证测试类中加上这两句, 同样,大家可以尝试一下错误的认证与授权, 看看会发生什么情况

	//判断是否具有此角色信息
	subject.checkRole("admin");
     //判断是否具有对应操作的权限
    subject.checkPermissions("user:add","user:delete");

Shiro加密

Shiro散列配置

  • HashedCredentialsMatcher
  • 自定义Realm中使用散列
  • 盐的使用

首先我们先要创建一个HashedCredentialsMatcher工具类

	@Test
    public void testAuthentication(){

        CustomRealm customRealm = new CustomRealm();

        //1. 构建SecurityManager环境
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(customRealm);//配置环境

        //创建工具类
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //设置加密的名称,加密方式
        matcher.setHashAlgorithmName("md5");
        //设置加密的次数
        matcher.setHashIterations(1);
        //在自定义的Realm中配置HashedCredentialsMatcher对象
        customRealm.setCredentialsMatcher(matcher);


        //2. 主体提交认证请求
        SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
        Subject subject = SecurityUtils.getSubject();//获得主体
        UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
        subject.login(token);//进行验证

        //是否认证成功
        System.out.println(subject.isAuthenticated());

//        subject.checkRole("admin");
//
//        //判断是否具有对应操作的权限
//        subject.checkPermissions("user:add","user:delete");
    }

在自定义类中创建main方法, 使用md5获得加密后的密码

		public static void main(String[] args) {
        //得到加密数据
        Md5Hash md5Hash = new Md5Hash("123");
        System.out.println(md5Hash.toString());
   	 }

更改map里的密码为加密后的数据(数据库里的密码都是经过加密的只不过我这里的map是用来模拟)

但是由于密码只进行了一次加密, 安全级别还是达不到我们所需要的级别, 所以我们需要进行加盐.
获得加盐数据

 public static void main(String[] args) {
        //得到加盐数据
        Md5Hash md5Hash = new Md5Hash("123","ljj");
        System.out.println(md5Hash.toString());
    }

更改map里面的密码
我们还需要在认证方法中添加这样一句代码
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(“ljj”)); 需要把所加的盐设置进SimpleAuthenticationInfo

	 /**
     * 认证
     *
     * @param authenticationToken 认证信息
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //1.从主体传过来的认证信息中,获取用户名
        String userName = (String) authenticationToken.getPrincipal();

        //2.通过用户名到数据库中获取凭证
        String password = getPasswordByUserName(userName);
        //如果不存在
        if (password == null) {
            return null;
        }
        //创建SimpleAuthenticationInfo,返回数据
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                "ljj", password, "customRealm");
        //需要把所加的盐设置进来               直接将字符串转换成ByteSource对象
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("ljj"));
        return authenticationInfo;
    }

在下一篇博客中, 我将带着大家从数据库和缓存中获取数据, 跟真实的项目完全一致, 谢谢大家的关注

springBoot整合Shiro实战教程

https://blog.csdn/I_No_dream/article/details/93227335

更多推荐

Shiro基础教程