• Apache Shiro 认证+授权(一)


    1、核心依赖

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.4.0</version>
            </dependency>
    

    2、认证流程:创建SecurityManager-->主体提交认证-->SecurityMananger认证-->Authentictor认证-->Realm验证(从subject.login(token)开始跟踪源码可以验证(idea下ctrl+alt+b跟踪源码)),单元测试代码如下:

    package com.example.demo_mg;
    
    import junit.framework.Assert;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.SimpleAccountRealm;
    import org.apache.shiro.subject.Subject;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
    
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    
        @Before
        public void before() {
            simpleAccountRealm.addAccount("wzs", "123456");
        }
    
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(simpleAccountRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            Assert.assertEquals(subject.isAuthenticated(), true);
            subject.logout();
            Assert.assertEquals(subject.isAuthenticated(), true);
        }
    }
    

    账号错误UnknownAccountException,密码错误IncorrectCredentialsException。

    从subject.login(token);点击ctrl+alt+b跟踪源码到DelegatingSubject的login方法,调用Subject subject = this.securityManager.login(this, token);,继续跟踪到DefaultSecurityManager的login方法,调用info = this.authenticate(token);,继续跟踪到AuthenticatingSecurityManager的authenticate方法,调用this.authenticator.authenticate(token);,继续跟踪到AbstractAuthenticator的authenticate方法,调用info = this.doAuthenticate(token);,继续跟踪到ModularRealmAuthenticator的doAuthenticate方法,可以看到如下认证代码,通过realm实现认证:

        protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
            this.assertRealmsConfigured();
            Collection<Realm> realms = this.getRealms();
            return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
        }
    

    3、授权流程:创建SecurityManager-->主体授权-->SecurityManager授权-->Authorizer授权-->Realm获取角色权限数据(数据库|缓存等):

    package com.example.demo_mg;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.SimpleAccountRealm;
    import org.apache.shiro.subject.Subject;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
    
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    
        @Before
        public void before() {
            simpleAccountRealm.addAccount("wzs", "123456", "admin", "user");
        }
    
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(simpleAccountRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            //授权
            subject.checkRole("admin");
            subject.checkRoles("admin", "user");
            subject.logout();
        }
    }
    

    角色不存在UnauthorizedException

    从subject.checkRoles("admin", "user");点击ctrl+alt+b跟踪源码到DelegatingSubject的checkRoles方法,调用this.securityManager.checkRoles(this.getPrincipals(), roleIdentifiers);,继续跟踪到AuthorizingSecurityManager的checkRoles方法,调用this.authorizer.checkRoles(principals, roles);,继续跟踪到ModularRealmAuthorizer的checkRoles方法,遍历roles调用this.checkRole(principals, role);,继续跟踪到checkRole方法,调用this.hasRole(principals, role),继续跟踪到hasRole方法,可见如下代码,通过realm授权:

        public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
            this.assertRealmsConfigured();
            Iterator var3 = this.getRealms().iterator();
    
            Realm realm;
            do {
                if (!var3.hasNext()) {
                    return false;
                }
    
                realm = (Realm)var3.next();
            } while(!(realm instanceof Authorizer) || !((Authorizer)realm).hasRole(principals, roleIdentifier));
    
            return true;
        }
    

    4、测试内置的IniRealm:
    在src/test下新建Direcotry,名称resources,右键Mark Direcotry As-->Test Sources Root,在下面新建user.ini文件,内容:

    [users]
    wzs=123456,admin
    [roles]
    admin=user:delete,user:update
    

    单元测试代码:

    package com.example.demo_mg;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
    
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            IniRealm iniRealm = new IniRealm("classpath:user.ini");
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(iniRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            //授权
            subject.checkRole("admin");
            subject.checkPermissions("user:delete", "user:update");
            subject.checkPermission("user:insert");
            subject.logout();
        }
    }
    

    权限不存在UnauthorizedException

    5、测试内置的JdbcRealm:
    1)安装mysql8.0
    2)按照JdbcRealm的源码的SQL建表:

    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL,
      `password` varchar(255) DEFAULT NULL,
    `password_salt` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
    
    CREATE TABLE `user_roles` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL,
      `role_name` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
    
    CREATE TABLE `roles_permissions` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `role_name` varchar(255) DEFAULT NULL,
      `permission` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
    

    3)添加测试数据:

    insert into users (username,password) values ('wzs','123456');
    insert into user_roles (username,role_name) values ('wzs','admin');
    insert into roles_permissions (role_name,permission) values ('admin','user:delete');
    

    4)依赖

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.11</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.6</version>
            </dependency>
    

    5)单元测试代码:

    package com.example.demo_mg;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.jdbc.JdbcRealm;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
        DruidDataSource dataSource = new DruidDataSource();
        {
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://129.204.58.30:3306/shiro");
            dataSource.setUsername("user");
            dataSource.setPassword("*********");
        }
    
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            JdbcRealm jdbcRealm = new JdbcRealm();
            jdbcRealm.setDataSource(dataSource);
            jdbcRealm.setPermissionsLookupEnabled(true);//为了查询权限表,要开启权限查询
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(jdbcRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            //授权
            System.out.println(subject.isAuthenticated());
            System.out.println(subject.hasRole("admin"));
            System.out.println(subject.isPermitted("user:delete"));
            subject.logout();
        }
    }
    

    6)若自定义用户、角色、权限三张表,需要自定义查询用户、角色、权限的SQL:

    参考JdbcRealm的查询语句:
        protected String authenticationQuery = "select password from users where username = ?";
        protected String userRolesQuery = "select role_name from user_roles where username = ?";
        protected String permissionsQuery = "select permission from roles_permissions where role_name = ?";
    
    设置自定义查询语句到jdbcRealm对象:
            jdbcRealm.setAuthenticationQuery("自定义查询用户sql");
            jdbcRealm.setUserRolesQuery("自定义查询角色sql");
            jdbcRealm.setPermissionsQuery("自定义查询权限sql");
    

    6、自定义Realm,参考JdbcRealm继承抽象的AuthorizingRealm类并实现其抽象方法,认证doGetAuthenticationInfo,授权doGetAuthenticationInfo,然后单元测试授权和认证:

    package com.example.demo_mg.realm;
    
    import org.apache.commons.collections.map.HashedMap;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    import java.util.*;
    
    public class TestRealm extends AuthorizingRealm {
        //模拟users、user_roles、roles_permissions三张表的查询,实际应用需要查询数据库或缓存
        Map<String, String> users = new HashMap<>();
        Map<String, Set<String>> user_roles = new HashedMap();
        Map<String, Set<String>> roles_permissions = new HashedMap();
    
        {
            users.put("wzs", "123456");
            user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
            roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
            super.setName("TestRealm"); //设置Realm名称,可选
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //从认证信息获取用户名
            String username = (String)principalCollection.getPrimaryPrincipal();
            //从数据库或缓存中获取角色、权限数据
            Set<String> roles = user_roles.get(username);
            Set<String> permissions = new HashSet<>();
            for (String role : roles) {
                Set<String> set;
                if((set = roles_permissions.get(role)) != null) {
                    permissions.addAll(set);
                }
            }
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.setRoles(roles);
            simpleAuthorizationInfo.setStringPermissions(permissions);
            return simpleAuthorizationInfo;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //从主题传过来的认证信息中,获得用户名
            String username = (String)authenticationToken.getPrincipal();
            //通过用户名从数据库中获取凭证
            String password = users.get(username);
            if(password != null) {
                return new SimpleAuthenticationInfo(username, password, super.getName());
            }
            return null;
        }
    }
    

    单元测试:

    package com.example.demo_mg;
    
    import com.example.demo_mg.realm.TestRealm;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            TestRealm testRealm = new TestRealm();
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(testRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            //授权
            System.out.println(subject.isAuthenticated());
            System.out.println(subject.hasRole("admin"));
            System.out.println(subject.isPermitted("user:delete"));
            subject.logout();
        }
    }
    

    7、密码加密及加盐:
    在6的基础上调整代码:

    package com.example.demo_mg.realm;
    
    import org.apache.commons.collections.map.HashedMap;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    import java.util.*;
    
    public class TestRealm extends AuthorizingRealm {
        //模拟users、user_roles、roles_permissions三张表的查询,实际应用需要查询数据库或缓存
        Map<String, String> users = new HashMap<>();
        Map<String, Set<String>> user_roles = new HashedMap();
        Map<String, Set<String>> roles_permissions = new HashedMap();
        String salt = UUID.randomUUID().toString().replaceAll("-","");
    
        {
            //不加盐(与认证对应)
    //        users.put("wzs", new Md5Hash("123456",null, 2).toString());
            //加盐
            users.put("wzs", new Md5Hash("123456",salt, 2).toString());
            user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
            roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
            super.setName("TestRealm"); //设置Realm名称,可选
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //从认证信息获取用户名
            String username = (String)principalCollection.getPrimaryPrincipal();
            //从数据库或缓存中获取角色、权限数据
            Set<String> roles = user_roles.get(username);
            Set<String> permissions = new HashSet<>();
            for (String role : roles) {
                Set<String> set;
                if((set = roles_permissions.get(role)) != null) {
                    permissions.addAll(set);
                }
            }
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.setRoles(roles);
            simpleAuthorizationInfo.setStringPermissions(permissions);
            return simpleAuthorizationInfo;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //从主题传过来的认证信息中,获得用户名
            String username = (String)authenticationToken.getPrincipal();
            //通过用户名从数据库中获取凭证
            String password = users.get(username);
            if(password != null) {
                //不加盐
    //            return new SimpleAuthenticationInfo(username, password, super.getName());
                //加盐
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, super.getName());
                simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt));
                return simpleAuthenticationInfo;
            }
            return null;
        }
    }
    

    单元测试:

    package com.example.demo_mg;
    
    import com.example.demo_mg.realm.TestRealm;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoMgApplicationTests {
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() {
            TestRealm testRealm = new TestRealm();
    
            //设置加密
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
            matcher.setHashAlgorithmName("md5"); //加密算法
            matcher.setHashIterations(2); //加密次数,与new Md5Hash("123456",null, 2).toString();的hashIterations参数一致,否则IncorrectCredentialsException
            testRealm.setCredentialsMatcher(matcher);
    
            //构建SecurityManager环境
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
            defaultSecurityManager.setRealm(testRealm);
            //主体提交认证请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
            subject.login(token);
            //授权
            System.out.println(subject.isAuthenticated());
            System.out.println(subject.hasRole("admin"));
            System.out.println(subject.isPermitted("user:delete"));
            subject.logout();
        }
    }
    
    

    MD5加密不可逆,但不加盐存在风险,注册的时候随机生成盐,保存到数据库,这里返回的认证对象带的盐从库里查出来。

  • 相关阅读:
    mysql常用sql语句的练习笔记
    docker-compose使用--config启动mongodb出错的采坑记录
    ubuntu1804安装docker和docker-compose的最新2020详细教程
    ubuntu1804使用国内git源安装fastdfs的笔记
    2020最新nginx+gunicorn+supervisor部署基于flask开发的项目的生产环境的详细攻略
    2020年ubuntu1804安装php7.3最新详细教程
    2020年ubuntu1804安装nginx最新稳定版1.16详细教程笔记
    ubuntu1804python安装mysqlclient的模块报错的解决办法
    ubuntu1804开启mysql远程访问功能和设置root远程访问
    ubuntu1804使用python3 venv 创建虚拟目录和制定Pip国内安装源
  • 原文地址:https://www.cnblogs.com/kibana/p/11105372.html
Copyright © 2020-2023  润新知