shiro与spring集成---安全框架集成spring

# Apache Shiro是什么?
 apache Shiro是一个强大且易用的Java安全框架,能够执行身份验证、授权、加密和会话管理。
 
# apache Shiro的主要API

## Subject
  Subject即“当前操作用户”。但是,在Shiro中,Subject这一概念并不是"帐户",而是与当前跟shrio交互的应用。
 
## SecurityManager
  SecurityManager是Shiro框架的核心,典Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

## Realm
  Realm是安全数据源,其中包含认证和授权数据。
     也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
     当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。
  Shiro内置了可以连接大量安全数据源(又名目录)的Realm, 
     如果缺省的Realm不能满足需求,可以自定义Realm实现。 
     
# 使用Apache Shiro

## 引入依赖
    <!-- Shiro核心包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    
    <!-- Shiro Web支持包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.4.0</version>
    </dependency>
    
    <!-- Shiro与Spring的集成包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>

## 在web.xml配置Shiro的代理过滤器
  说明:该过滤器仅仅是过滤器代理,其功能由spring中配置的shrio过滤器实现
  
    <!-- 此shiro代理过滤器的名称“shiroFilter”应当和Spring配置文件中的Shiro过滤 bean名称相匹配一致 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <!-- 
        确保所有的需要通过Shrio的请求被过滤。/*意味着拦截所有请求。 
        通常这个过滤器配置在所有过滤器之前,以 确保Shiro能够工作在过滤器链中随后的过滤器中。
    -->
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    
## 开发自定义Realm

/**
 * 通过继承AuthorizingRealm实现自定义Realm。
 */
public class SaftyRealm extends AuthorizingRealm {

    @Resource
    private SaftyService saftyService;

    /**
     * 获取当事人(当前用户)授权信息 参数PrincipalCollection 当事人集合
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        /*
         * 获取登录用户帐号
         */
        CurrUserDto currUser = (CurrUserDto) principalCollection.getPrimaryPrincipal();// 获取首要(第一)当事人

        /*
         * 创建授权信息对象
         */
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        
//                 
//        // 查询角色
//        List<Role> roleList = saftyService.getRolesOfUser(currUser.getUserId());
//
//        // 将角色信息放入授权信息对象         
//        for (Role role : roleList) {
//            simpleAuthorizationInfo.addRole(String.valueOf(role.getRo_id()));
//        }
//        
        
                
        /*
         * 查询用户权限,并将权限放入授权信息对象中         
         */
        List<Module> moduleList = saftyService.getModulesOfUser(currUser.getUserId());
        for (Module module : moduleList) {
            simpleAuthorizationInfo.addStringPermission(String.valueOf(module.getM_id()));
        }

        // System.out.println(currUser.getUserId()+"->"+simpleAuthorizationInfo.getStringPermissions());

        /*
         * 返回授权信息
         */
        return simpleAuthorizationInfo;
    }

    /**
     * 获取认证信息 参数 AuthenticationToken 认证令牌(如:一组用户名和密码就是一个认证令牌)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  {

        String userId = (String) token.getPrincipal();// 获得当事人(当前用户)

        User user = saftyService.getUser(userId);

        /*
         * 如果不存在前用户信息,返回null
         */
        if (user == null) {
            return null;
        }

        /*
         * 创建当前用户
         */
        CurrUserDto currUser = new CurrUserDto();
        currUser.setUserId(user.getU_id());
        currUser.setUserName(user.getU_name());
        
        /*
         * 创建认证信息,三个构造参数含义依次如下:
         *  参数1:principal当前用户 
         *  参数2:credentials认证凭证(如:口令、密码等)
         *  参数3:realm名称
         */
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currUser, user.getU_pwd(), this.getName());

        /*
         * 返回认证信息
         */
        return info;
    }

}

## 在spring配置文件中配置Shiro
 
    <!-- 保证实现了Shiro内部lifecycle(回调)函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> 
    
    <!-- 配置自定义 realm -->
    <bean id="saftyRealm" class="com.tnr.scgcxx.shiro.SaftyRealm"/>
    
    <!-- 配置自定义securityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"   >
        <property name="realm" ref="saftyRealm" />
        <property name="sessionMode" value="native"/><!-- 配置使用Shiro本地Session,该Session会与HttpSession数据同步 -->
    </bean>
    
    <!-- 配置Shiro过滤器,该过滤器名称应当与web.xml配置的代理过滤器名称一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        
        <property name="securityManager" ref="securityManager"/><!-- 必须设置 -->
        
        <!-- 3 个 url 属性为可选设置 -->
        <property name="loginUrl" value="/safty/login_to"/> <!-- 登录界面url -->
        <property name="successUrl" value="/safty/home_to"/> <!-- 登录成功后界面url -->
        <property name="unauthorizedUrl" value="/safty/unauthorized_to"/> <!-- 未授权界面url -->
        
        <!-- 
            定义过滤器链,即定义shiro的ini配置信息(拦截规则),说明如下:
            /static/** = anon 以路径“/static/”开头的所有地址均可匿名访问,无需认证
            /safty/login_to = anon 路径“/safty/login_to”可匿名访问,无需认证
            /safty/login = anon 路径“/safty/login”可匿名访问,无需认证
            /** = authc 所有路径需认证。注:此规则写在最后,表示之前规则生效的前提下,无法匹配之前规则情况下,若符合此规则则按此规则执行。
        -->
        <property name="filterChainDefinitions">
            <value>               
                /static/** = anon                
                /safty/login_to = anon
                /safty/login = anon                
                /** = authc
            </value>
        </property>
        
        
    </bean>
    

## 控制器中的登录代码示例

    @RequestMapping(value="/login",method=RequestMethod.POST)
    public ResultDto login(@RequestBody UserDto userDto,HttpSession session) {
                
        try {
            Subject subject = SecurityUtils.getSubject();
            
            //创建登录令牌
            UsernamePasswordToken usernamePasswordToken = 
                    new UsernamePasswordToken(userDto.getU_id(),userDto.getU_pwd());
            
            //登录,该方法声明抛出异常,可以捕获异常,并进行应对处理
            subject.login(usernamePasswordToken);
            
            //是否通过认证
            if(subject.isAuthenticated()) {
                
                //获得当前用户信息
                CurrUserDto currUser = (CurrUserDto)subject.getPrincipal();
                
                //将当前用户放入Session。注:这里的Session是由Shiro提供的。
                subject.getSession().setAttribute(Constants.SESSION_ATTR_NAME_CURR_USER, currUser);
                
                
                return ResultDto.successResult();
            }
            
            return ResultDto.failResult("登录失败!");
            
        } catch (UnknownAccountException e) {
            return ResultDto.failResult("用户名不存在!");
            
        } catch (IncorrectCredentialsException e) {
            return ResultDto.failResult("账户密码 不正确!");
        } catch (LockedAccountException e) {
            return ResultDto.failResult("用户名 被锁定 !");
        }catch (Exception e) {
            e.printStackTrace();
            return ResultDto.failResult("系统错误!");
        }
        
    }

    
## 控制器中的退出代码示例    

    @DeleteMapping("/logout")
    public ResultDto login_authentication() {
        
        try {
            //注销
            SecurityUtils.getSubject().logout();
            return ResultDto.successResult();
        } catch (Exception e) {
            return ResultDto.failResult("退出失败!");
        }
    }

## 动态权限控制
   
   上述spring中ShiroFilter配置拦截规则如下:
   <property name="filterChainDefinitions">
        <value>               
            /static/** = anon                
            /safty/login_to = anon
            /safty/login = anon                
            /** = authc
        </value>
    </property>
    
  显然,这些拦截规则仅包含认证部分,缺少授权部分,授权配置形式如:/docs/** = authc, perms[xxx] 表示需要经过认证和xxx权限。
  而系统中的权限往往是动态的,随用户不同而不同,所以这种静态配置拦截规则的方式就不合适了。
  在这样的动态权限需求背景下,可以使用ShiroFilter另外一个Map类型属性filterChainDefinitionMap配置拦截规则,
  而filterChainDefinitionMap可以通过一个自定义的FactoryBean获得。
  
### 动态权限控制示例

/**
 * 自定义生成filterChainDefinitionMap的工厂bean
 */
public class ShiroDefinitionSectionFactory implements FactoryBean<Ini.Section> {
    
    private static final Logger LOG = LogManager.getLogger(ShiroDefinitionSectionFactory.class);
    
    public static final String PREMISSION_FORMAT = "authc,perms[{0}]";
   
    @Resource
    private SecurityManager securityManager;
    
    @Resource
    private SaftyService saftyService;
    
    /**
     * 注入先于动态权限加载的默认认证授权定义
     */
    private String preFilterChainDefinitions;
    
    
    /**
     * 注入后于动态权限加载的默认认证授权定义
     */
    private String postFilterChainDefinitions;
   
    @Override
    public Ini.Section getObject() throws Exception {
        
        /*
         * Ini是Map<String,Ini.Section>的实现类
         * Ini.Section是Map<String,String>的实现类,表示一个Ini配置片段实例
         */
        Ini ini = new Ini();
        
        //加载动态权限前的ini配置
        ini.load(preFilterChainDefinitions);
        
        //获取ini配置片段
        Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        
       
        /*
         * 加载动态权限
         */
        
        List<Module> moduleList = saftyService.getAllSubModules();
        
        //由注入的资源管理对象获取所有资源数据,并且资源的authorities的属性是EAGER的fetch类型
        for(Module module:moduleList) {
            if(StringUtils.isEmpty(module.getM_url())) {
                continue;
            }
            
            //将动态权限放入ini配置片段中
            section.put(module.getM_url(), MessageFormat.format(PREMISSION_FORMAT, String.valueOf(module.getM_id())) );
            
        }
        
        //加载动态权限之后的ini配置
        Ini postIni = new Ini();        
        postIni.load(postFilterChainDefinitions);        
        Ini.Section postSection=postIni.getSection(Ini.DEFAULT_SECTION_NAME);
       
        //将动态权限之后的ini配置放入统一的ini配置片段中
        section.putAll(postSection);
        
        LOG.debug("=====Shiro安全规则=======================================================================");
        LOG.debug(section.entrySet());
        LOG.debug("=====Shiro安全规则=======================================================================");
        
        return section;
    }

    
   

    public void setPreFilterChainDefinitions(String preFilterChainDefinitions) {
        this.preFilterChainDefinitions = preFilterChainDefinitions;
    }


    public void setPostFilterChainDefinitions(String postFilterChainDefinitions) {
        this.postFilterChainDefinitions = postFilterChainDefinitions;
    }


    @Override
    public Class<?> getObjectType() {
        return this.getClass();
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

}

    修改spring配置文件
   
    <!-- 配置自定义的生成filterChainDefinitionMap的工厂bean -->
       <bean id="filterChainDefinitionMap" class="com.tnr.scgcxx.shiro.ShiroDefinitionSectionFactory">
        <property name="preFilterChainDefinitions">
            <value>
                /static/** = anon                
                /safty/login_to = anon
                /safty/login = anon
            </value> 
        </property>
         <property name="postFilterChainDefinitions">
            <value>
                /** = authc
            </value> 
        </property>
    </bean>

 
 
 
 注:引入shiro时,shiro自动引入一个日志工具依赖slf4j,
 该工具与spring-jcl冲突 导致mybatis日志不输出,需要在pom.xml添加如下依赖消除冲突:
    <!-- 
        以下配置作用是在spring中使用slf4j
     -->
     
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.0.7.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jcl</artifactId>
            </exclusion>        
        </exclusions>
    </dependency>
     
    <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.25</version>
    </dependency>
     
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.11.0</version>
    </dependency>
    <!-- 
        以上配置作用是在spring中使用slf4j
    --> 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值