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

## 引入依赖
    
    
        org.apache.shiro
        shiro-core
        1.4.0
    

    
    
    
        org.apache.shiro
        shiro-web
        1.4.0
    

    
    
    
        org.apache.shiro
        shiro-spring
        1.4.0
    

## 在web.xml配置Shiro的代理过滤器
  说明:该过滤器仅仅是过滤器代理,其功能由spring中配置的shrio过滤器实现
  
    
    
        shiroFilter
        org.springframework.web.filter.DelegatingFilterProxy
        
            targetFilterLifecycle
            true
        

    

    
    
        shiroFilter
        /*
    

    
## 开发自定义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 roleList = saftyService.getRolesOfUser(currUser.getUserId());
//
//        // 将角色信息放入授权信息对象         
//        for (Role role : roleList) {
//            simpleAuthorizationInfo.addRole(String.valueOf(role.getRo_id()));
//        }
//        
        
                
        /*
         * 查询用户权限,并将权限放入授权信息对象中         
         */
        List 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
 
    
     
    
    
    
    
    
    
        
        
    

    
    
    
        
        
        
        
        
        
        
        
        
        
                           
                /static/** = anon                
                /safty/login_to = anon
                /safty/login = anon                
                /** = authc
            

        

        
        
    

    

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

    @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配置拦截规则如下:
   
                       
            /static/** = anon                
            /safty/login_to = anon
            /safty/login = anon                
            /** = authc
       

   

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

/**
 * 自定义生成filterChainDefinitionMap的工厂bean
 */
public class ShiroDefinitionSectionFactory implements FactoryBean {
    
    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的实现类
         * Ini.Section是Map的实现类,表示一个Ini配置片段实例
         */
        Ini ini = new Ini();
        
        //加载动态权限前的ini配置
        ini.load(preFilterChainDefinitions);
        
        //获取ini配置片段
        Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        
       
        /*
         * 加载动态权限
         */
        
        List 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配置文件
   
   
       
        
            
                /static/** = anon                
                /safty/login_to = anon
                /safty/login = anon
            
 
        

        
            
                /** = authc
            
 
        

    

 
 
 
 注:引入shiro时,shiro自动引入一个日志工具依赖slf4j,
 该工具与spring-jcl冲突 导致mybatis日志不输出,需要在pom.xml添加如下依赖消除冲突:
    
     
    
    
        org.springframework
        spring-core
        5.0.7.RELEASE
        
            
                org.springframework
                spring-jcl
            
        
        

    

     
    
    
        org.slf4j
        jcl-over-slf4j
        1.7.25
    

     
    
        org.slf4j
        slf4j-api
        1.7.25
    

    
        org.apache.logging.log4j
        log4j-slf4j-impl
        2.11.0
    

     
 

你可能感兴趣的:(交流学习)