shiro

头像
SuSen
    阅读 14 分钟

    概念

    Apache Shiro 是java的一个安全框架,Shiro可以非常容易的开发出足够好的应用,其不仅可以用在javaSE环境,也可以用在javaEE环境。Shiro可以帮助我们完成:认证,授权,加密,会话,管理,与Web集合,缓存等。
    Java领域中spring security也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单,灵活,所以现在越来越多的用户选择shiro。

    基本功能

    image-20191203112031777.png

    Authentication

        身份认证/登录,验证用户是不是拥有相应的身份

    Authorization

        授权,即权限验证,验证某个已认证的用户是否拥有某个权限,判断用户是否能做事情,常见的如:验证某个用是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限

    Session Manager

        会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中,会话可以是普通javaSE环境的,也可以是如Web环境的。
    

    Cryptography

        加密,保护数据的安全性,如密码加密储存到数据库,而不是明文存储。
    • Web Support:Web 支持,可以非常容易的集成到web环境。
    • Caching:缓存,比如用户登录后,其用户信息,用户的角色/权限不必每次去查,这样可以提高效率。
    • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。
    • Testing:提供测试支持。
    • Run As:允许一个用户假装为另一个用户的身份进行访问。
    • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
    • Shiro:不会去维护用户,维护权限,这些需要我们自己去设计/提供然后通过相应的接口注入给Shiro即可。

    架构

    image-20191204153257793.png

    subject

    Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。  Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

    SecurityManager

    SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。
    通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,
    通过Authorizer进行授权,通过SessionManager进行会话管理等。
    SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

    Authenticator

    Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,
    通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

    Authorizer

    Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

    realm

    Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
    注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码

    sessionManager

    sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

    SessionDAO

    SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

    CacheManager

    CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

    Cryptography

    Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

    默认过滤器

    Filter Name Class
    anon org.apache.shiro.web.filter.authc.AnonymousFilter
    authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    logout org.apache.shiro.web.filter.authc.LogoutFilter
    noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
    perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
    port org.apache.shiro.web.filter.authz.PortFilter
    rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    ssl org.apache.shiro.web.filter.authz.SslFilter
    user org.apache.shiro.web.filter.authc.UserFilter

    shiro.ini

    shiro.ini配置文件共有[main],[users],[roles],[urls]共4部分组成
    
    1.  [main] 用于定义全局变量
    
    2.  [users] 用于定义用户名及密码
    
    3.  [roles] 用于定义角色
    
    4.  [urls] 用于定义访问url及拦截验证方式

    [main]

    #提供了对根对象 securityManager 及其依赖的配置
    securityManager=org.apache.shiro.mgt.DefaultSecurityManager
    jdbcRealm = xxxxx
    securityManager.realms=$jdbcRealm

    [users]

    #提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2 username=password,role1,role2
    #定义用户信息
    [users]
    #用户名=密码
    admin=123456
    test=123

    [roles]

    #提供了角色及权限之间关系的配置,角色=权限 1,权限 2 role1=permission1,permission2
    [roles]
    role1=admin:query,admin:add,admin:update,admin:delete,admin:export
    role2=user:query,user:add
    role3=test:query,test:export

    [urls]

    #用于 web,提供了对 web url 拦截相关的配置,url=拦截器[参数],拦截器
    

    Realm域

    Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource ,即安全数据源。如我们之前的ini配置方式 将使用org.apache.shiro.realm.text.IniRealm。
    Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,
    **所以需要自定义realm**

    jar包

        <!-- shiro核心依赖 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.2</version>
    </dependency>

    Realm

    package com.bdqn.shiro;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.jdbc.JdbcRealm;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.Factory;
    
    public class JDBCRealmTest {
    
         public static void main(String[] args) {
            //1.读取配置文件,初始化工厂对象
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            //2.获取SecurityManager实例
            SecurityManager manager = factory.getInstance();
            //3.将SecurityManager绑定到工具类
            SecurityUtils.setSecurityManager(manager);
            //4.通过SecurityUtils得到当前登录的用户
            Subject currentUser = SecurityUtils.getSubject();
            //5.窗口登录令牌
            UsernamePasswordToken token = new UsernamePasswordToken("test","123");
            try {
                //6.登录并传入令牌
                currentUser.login(token);
                System.out.println("身份信息验证成功!");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("身份信息验证失败!");
            }
            //7.退出
            currentUser.logout();
        }
    }

    加到项目中的验样子

    Spring Boot框架
    shiro
    mysql


    • 配置 application.properties
    #加载驱动
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    #数据库连接路径
    spring.datasource.url=jdbc:mysql://localhost:3306/erp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    #数据库用户名
    spring.datasource.username=root
    #数据源类型(阿里巴巴)
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    #thymeleaf
    spring.thymeleaf.cache=false
    #日期格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    #mybatis-plus
    #mybatis-plus.mapper-locations=classpath:mapper/*Mapper.xml,classpath:mapper/*/*Mapper.xml
    #日志
    logging.level.com.bdqn=debug
    • ShiroConfiguration 类
    package com.bdqn.sys.config;
    
    import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
    import com.bdqn.sys.realm.UserRealm;
    import org.apache.shiro.authc.credential.CredentialsMatcher;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.filter.DelegatingFilterProxy;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfiguration {
        private static final String SHIRO_DIALECT = "shiroDialect";
        private static final String SHIRO_FILTER = "shiroFilter";
        private String hashAlgorithmName = "md5";// 加密方式
        private int hashIterations = 2;// 散列次数
    
    
        /**
         * 声明凭证匹配器
         */
        @Bean("credentialsMatcher")
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
            credentialsMatcher.setHashIterations(hashIterations);
            return credentialsMatcher;
        }
    
        /**
         * 注入自定义的UserRealm
         * @return
         */
        @Bean
        public UserRealm getUserRealm(CredentialsMatcher credentialsMatcher){
            UserRealm userRealm = new UserRealm();
            //注入凭证匹配器
            userRealm.setCredentialsMatcher(credentialsMatcher);
            return userRealm;
        }
    
    
        /**
         * 创建DefaultWebSecurityManager对象,关联自定义的UserRealm对象
         * @param userRealm
         * @return
         */
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
            //创建DefaultWebSecurityManager对象
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            //关联自定义realm
            defaultWebSecurityManager.setRealm(userRealm);
            //返回DefaultWebSecurityManager对象
            return defaultWebSecurityManager;
        }
    
        /**
         * 创建ShiroFilterFactoryBean对象,设置安全管理器
         * @param securityManager
         * @return
         */
        @Bean(SHIRO_FILTER)
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
            //创建ShiroFilterFactoryBean对象
            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
            //设置安全管理器
            factoryBean.setSecurityManager(securityManager);
            //设置过滤器链
            Map<String,String> filterChainDefinitionsMap = new LinkedHashMap<String,String>();
            //放行路径(匿名访问)
            filterChainDefinitionsMap.put("/resources/**","anon");//静态资源
            filterChainDefinitionsMap.put("/sys/user/login","anon");//登录请求
            filterChainDefinitionsMap.put("/sys/login","anon");//去到登录页面
            filterChainDefinitionsMap.put("/","anon");//去到登录页面
            filterChainDefinitionsMap.put("/login.html","anon");//去到登录页面
            filterChainDefinitionsMap.put("/favicon.ico","anon");//小图标
            //退出
            filterChainDefinitionsMap.put("/logout","logout");
            //拦截请求
            filterChainDefinitionsMap.put("/**","authc");
            //将过滤器链设置到shiroFilterFactoryBean对象中
            factoryBean.setFilterChainDefinitionMap(filterChainDefinitionsMap);
            //身份验证失败要去到登录页面
            //如果不设置loginUrl,则默认找login.jsp页面
            factoryBean.setLoginUrl("/sys/login");
            return factoryBean;
        }
    
        /**
         * 注册shiro的委托过滤器,相当于之前在web.xml里面配置的
         *
         * @return
         */
        @Bean
        public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
            FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
            DelegatingFilterProxy proxy = new DelegatingFilterProxy();
            proxy.setTargetFilterLifecycle(true);
            proxy.setTargetBeanName(SHIRO_FILTER);
            filterRegistrationBean.setFilter(proxy);
            return filterRegistrationBean;
        }
        /* 加入注解的使用,不加入这个注解不生效--开始 */
        /**
         *
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        @Bean
        public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            advisorAutoProxyCreator.setProxyTargetClass(true);
            return advisorAutoProxyCreator;
        }
        /* 加入注解的使用,不加入这个注解不生效--结束 */
    
        /**
         * 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,不然会报错
         *
         * @return
         */
        @Bean(name = SHIRO_DIALECT)
        public ShiroDialect shiroDialect() {
            return new ShiroDialect();
        }
    }
    • Realm 类
    package com.bdqn.sys.realm;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.bdqn.sys.entity.Permission;
    import com.bdqn.sys.entity.User;
    import com.bdqn.sys.service.PermissionService;
    import com.bdqn.sys.service.RoleService;
    import com.bdqn.sys.service.UserService;
    import com.bdqn.sys.utils.SystemConstant;
    import com.bdqn.sys.vo.LoginUserVo;
    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 org.apache.shiro.util.ByteSource;
    
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    /**
     * 自定义Realm
     */
    public class UserRealm extends AuthorizingRealm {
    
    
        @Resource
        private UserService userService;
    
        @Resource
        private RoleService roleService;
    
        @Resource
        private PermissionService permissionService;
    
    
    
        /**
         * 授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //创建授权对象
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            //1.获取当前登录主体
            LoginUserVo loginUserVo = (LoginUserVo) principalCollection.getPrimaryPrincipal();
            //2.获取当前用户拥有的权限列表
            Set<String> permissions = loginUserVo.getPermissions();
            //3.判断当前登录用户是否是超级管理员
            if(loginUserVo.getUser().getType()== SystemConstant.SUPERUSER){
                //超级管理员拥有所有操作权限
                simpleAuthorizationInfo.addStringPermission("*:*");
            }else{//普通用户
                //判断权限集合是否有数据
                if(permissions!=null && permissions.size()>0){
                    //非超级管理员需要根据自己拥有的角色进行授权
                    simpleAuthorizationInfo.addStringPermissions(permissions);
                }
            }
            return simpleAuthorizationInfo;
        }
    
        /**
         * 身份验证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //获取当前登录主体
            String userName = (String) authenticationToken.getPrincipal();
            try {
                //根据用户名查询用户信息的方法
                User user = userService.findUserByName(userName);
                //对象不为空
                if(user!=null){
                    //创建当前登录用户对象
                    //创建登录用户对象,传入用户信息,角色列表,权限列表
                    LoginUserVo loginUserVo = new LoginUserVo(user,null,null);
                    /***************************关联权限代码开始***************************************/
                    //创建条件构造器对象
                    QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>();
                    queryWrapper.eq("type",SystemConstant.TYPE_PERMISSION);//只查权限不查菜单
    
                    //根据当前登录用户ID查询该用户拥有的角色列表
                    Set<Integer> currentUserRoleIds = userService.findUserRoleByUserId(user.getId());
                    //创建集合保存每个角色下拥有的权限菜单ID
                    Set<Integer> pids = new HashSet<Integer>();
                    //循环遍历当前用户拥有的角色列表
                    for (Integer roleId : currentUserRoleIds) {
                        //4.根据角色ID查询每个角色下拥有的权限菜单
                        Set<Integer> permissionIds = roleService.findRolePermissionByRoleId(roleId);
                        //5.将查询出来的权限id放到集合中
                        pids.addAll(permissionIds);
                    }
                    //创建集合保存权限
                    List<Permission> list = new ArrayList<Permission>();
                    //判断pids集合是否有值
                    if(pids!=null && pids.size()>0){
                        queryWrapper.in("id",pids);
                        //执行查询
                        list = permissionService.list(queryWrapper);
                    }
                    //查询权限编码
                    Set<String> perCodes = new HashSet<String>();
                    //循环每一个菜单
                    for (Permission permission : list) {
                        perCodes.add(permission.getPercode());
                    }
                    //给权限集合赋值
                    loginUserVo.setPermissions(perCodes);
    
                    /***************************关联权限代码结束***************************************/
                    //创建盐值(以用户名作为盐值)
                    ByteSource salt = ByteSource.Util.bytes(user.getSalt());
                    //创建身份验证对象
                    //参数1:当前登录对象  参数2:密码  参数3:盐值 参数4:域名
                    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(loginUserVo,user.getLoginpwd(),salt,"");
                    return info;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //验证失败
            return null;
        }
    
    
    }
    

    SuSen
    5 声望2 粉丝

    引用和评论

    0 条评论