Shiro安全框架

BolunWu

1.Shiro安全框架

1.1Shiro框架简介

Shiro是apache旗下一个开源安全框架(http://shiro.apache.org),它将...

1.2Shiro的概要架构

1)Subject:主体对象,负责提交用户认证和授权信息.
2)SecuritvManager:安全管理器,负责认证,授权等业务实现.
3)Realm:领域对象,负责从数据层获取业务数据.

1.3Shiro框架认证拦截实现

1.3.1Shiro基本环境配置

1)添加shiro依赖
<dependency>

   <groupId>org.apache.shiro</groupId>

   <artifactId>shiro-spring</artifactId>

   <version>1.5.3</version>

</dependency>
2)shiro核心对象配置
在SpringBoot项目中没有提供shiro的自动化配置,需要我们自己配置
第一步创建SpringShiroConfig类,关键代码如下:
package com.cy.pj.common.config;

/**@Configuration 注解描述的类为一个配置对象,

 * 此对象也会交给spring管理

 */

@Configuration

public class SpringShiroConfig {


}
第二步在Shiro配置类中添加SecurityManager配置(这里一定要使用org.apache.shiro.mgt.SecurityManager这个接口对象),关键代码如下:
@Bean

public SecurityManager securityManager() {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 return sManager;

}

第三步:在Shiro配置类中添加ShiroFilterFactoryBean对象的配置。通过此对象设置资源匿名访问,认证访问.关键代码如下:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map= new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }

1.4Shiro登录页面实现

业务描述当服务端拦截到用户请求以后,判定此请求是否已经被认证,假如没有认证应该先跳转到登录页面.
第一步在Controller层添加一个呈现登录页面的方法,关键代码如下:
@RequestMapping("doLoginUI")

public String doLoginUI(){

                return "login";

}
第二步:修改SpringShiroConfig类中shiroFilterFactorybean的配置,添加登录的url的设置,关键代码如下:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

 sfBean.setLoginUrl("/doLoginUI");

//定义map指定请求过滤规则(哪些资源允许匿名访问,

哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/modules/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

}

1.4Shiro框架认证业务实现

1.4.1认证流程分析

身份认证即判定用户是否是系统的合法用户,用户访问系统资源时的认证(对用户身份信息的认证)
1)系统调用subject的login方法将用户信息提交给SecurityManager
2)SecurityManager将认证操作委托给认证器对象Authenticator
3)Authenticator将用户输入的身份认证信息传递个Realm
4)Realm访问数据库获取用户信息然后对信息进行封装并返回
5)Authenticator对realm返回的信息进行身份认证.

1.4.2具体业务代码

Dao层
SysUser findUserByUserName(String username)。
SysUserMapper文件添加SQL
 <select id="findUserByUserName"

           resultType="com.cy.pj.sys.entity.SysUser">

      select *

      from sys_users  

      where username=#{username}

   </select>
Service接口及实现
第一步定义ShiroUserRealm类,关键代码如下:
package com.cy.pj.sys.service.realm;

@Service

public class ShiroUserRealm extends AuthorizingRealm {


        @Autowired

        private SysUserDao sysUserDao;

                

        /**

         * 设置凭证匹配器(与用户添加操作使用相同的加密算法)

         */

        @Override

        public void setCredentialsMatcher(

              CredentialsMatcher credentialsMatcher) {

                //构建凭证匹配对象

                HashedCredentialsMatcher cMatcher=

                new HashedCredentialsMatcher();

                //设置加密算法

                cMatcher.setHashAlgorithmName("MD5");

                //设置加密次数

                cMatcher.setHashIterations(1);

                super.setCredentialsMatcher(cMatcher);

        }

        /**

         * 通过此方法完成认证数据的获取及封装,系统

         * 底层会将认证数据传递认证管理器,由认证

         * 管理器完成认证操作。

         */

        @Override

        protected AuthenticationInfo doGetAuthenticationInfo(

                        AuthenticationToken token)

                        throws AuthenticationException {

                //1.获取用户名(用户页面输入)

                UsernamePasswordToken upToken=

                (UsernamePasswordToken)token;

                String username=upToken.getUsername();

                //2.基于用户名查询用户信息

                SysUser user=

                sysUserDao.findUserByUserName(username);

                //3.判定用户是否存在

                if(user==null)

                throw new UnknownAccountException();

                //4.判定用户是否已被禁用。

                if(user.getValid()==0)

                throw new LockedAccountException();

                

                //5.封装用户信息

                ByteSource credentialsSalt=

                ByteSource.Util.bytes(user.getSalt());

                //记住:构建什么对象要看方法的返回值

                SimpleAuthenticationInfo info=

                new SimpleAuthenticationInfo(

                                user,//principal (身份)

                                user.getPassword(),//hashedCredentials

                                credentialsSalt, //credentialsSalt

                                getName());//realName

                //6.返回封装结果

                return info;//返回值会传递给认证管理器(后续

                //认证管理器会通过此信息完成认证操作)

        }

    ....

}
第二步:对此realm,需要在SpringShiroConfig配置类中,注入SecurityManager对象,修改securityManager方法
@Bean

public SecurityManager securityManager(Realm realm) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 return sManager;

}
Controller层实现
第一步: 在SysUserController中添加处理登陆的方法.关键代码如下:
   public JsonResult doLogin(String username,String password){

                   //1.获取Subject对象

                   Subject subject=SecurityUtils.getSubject();

                   //2.通过Subject提交用户信息,交给shiro框架进行认证操作

                   //2.1对用户进行封装

                   UsernamePasswordToken token=

                   new UsernamePasswordToken(

                                   username,//身份信息

                                   password);//凭证信息

                   //2.2对用户信息进行身份认证

                   subject.login(token);

                   //分析:

                   //1)token会传给shiro的SecurityManager

                   //2)SecurityManager将token传递给认证管理器

                   //3)认证管理器会将token传递给realm

                   return new JsonResult("login ok");

           }
第二步: 修改shiroFilerFactory的配置,对/user/doLogin这个路径进行匿名访问的配置:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //假如没有认证请求先访问此认证的url

                 sfBean.setLoginUrl("/doLoginUI");

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");


  map.put("/user/doLogin","anon");                       //authc表示,除了匿名访问的资源,其它都要认证("authc")后才能访问访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }
第三步: 当我们在执行登录操作时,为了提高用户体验,可对系统中的异常信息进行处理,在统一的异常类中处理:
  @ExceptionHandler(ShiroException.class)

   @ResponseBody

        public JsonResult doHandleShiroException(

                        ShiroException e) {

                JsonResult r=new JsonResult();

                r.setState(0);

                if(e instanceof UnknownAccountException) {

                        r.setMessage("账户不存在");

                }else if(e instanceof LockedAccountException) {

                        r.setMessage("账户已被禁用");

                }else if(e instanceof IncorrectCredentialsException) {

                        r.setMessage("密码不正确");

                }else if(e instanceof AuthorizationException) {

                        r.setMessage("没有此操作权限");

                }else {

                        r.setMessage("系统维护中");

                }

                e.printStackTrace();

                return r;

        }

1.5Shiro框架授权过程实现

1.5.1 shiro框架授权过程分析

1)系统调用subject相关方法将用户信息(例如isPermitted)递交给SecurityManager.
2)SecurityManager将权限检测信息交给Authorize
3)Authorize将用户信息委托给realm
4)Realm访问数据库获取用户权限信息并封装
5)Autrorize对用户授权信息进行判定

1.5.2添加授权配置

在SpringShiroConfig类中添加授权时的相关配置:
第一步: 配置bean对象的生命周期管理(SpringBoot可以不用配置)
@Bean

public LifecycleBeanPostProcessor   lifecycleBeanPostProcessor() {

                 return new LifecycleBeanPostProcessor();

}
第二步: 通过如下配置为目标业务对象创建代理对象:(SpringBoot中可以省略)

@DependsOn("lifecycleBeanPostProcessor")

@Bean

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

                 return new DefaultAdvisorAutoProxyCreator();

}
第三步: 配置advisor对象,shiro框架会通过此对象的matchs方法返回值(类似于切入点)决定是否创建代理对象,进行权限控制.
@Bean

public AuthorizationAttributeSourceAdvisor

authorizationAttributeSourceAdvisor (

                                SecurityManager securityManager) {

                        AuthorizationAttributeSourceAdvisor advisor=

                                new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(securityManager);

        return advisor;

}

//说明:使用框架最重要的尊重规则,框架规则指定了什么方式就使用什么方式。

1.5.3授权代码实现

Dao实现
第一步: 在SysUserRoleDao中定义基于用户id查找角色id的方法:
   List<Integer> findRoleIdsByUserId(Integer id);
第二步: 在RoleMenuDao中定义基于角色id查找菜单的id方法:
  List<Integer> findMenuIdsByRoleIds(

                        @Param("roleIds")List<Integer> roleIds);
第三步: 在SysMenuDao中基于菜单id查找权限标识的方法:
 List<String> findPermissions(

                        @Param("menuIds")

                        List<Integer> menuIds);
Mapper实现
第一步: 在SysUserRoleMapper中定义findRoleIdsByUserId元素:
<select id="findRoleIdsByUserId"

            resultType="int">

           select role_id

           from sys_user_roles

           where user_id=#{userId}        

</select>
第二步: 在SysRoleMenuMapper中定义findMenuIdsByRoleIds元素:
 <select id="findMenuIdsByRoleIds"

         resultType="int">

         select menu_id

         from sys_role_menus

         where role_id in

         <foreach collection="roleIds"

                  open="("

                  close=")"

                  separator=","

                  item="item">

               #{item}

         </foreach>

</select>
第三步: 在SysMenuMapper中定义findPermission元素:
  <select id="findPermissions"

           resultType="string">

       select permission <!-- sys:user:update -->

       from sys_menus

       where id in

       <foreach collection="menuIds"

                open="("

                close=")"

                separator=","

                item="item">

            #{item}

       </foreach>

   </select>
Service实现
@Service

public class ShiroUserRealm extends AuthorizingRealm {

        @Autowired

        private SysUserDao sysUserDao;

        @Autowired

        private SysUserRoleDao sysUserRoleDao;

        @Autowired

        private SysRoleMenuDao sysRoleMenuDao;

        @Autowired

        private SysMenuDao sysMenuDao;

        /**通过此方法完成授权信息的获取及封装*/

        @Override

        protected AuthorizationInfo doGetAuthorizationInfo(

                PrincipalCollection principals) {

                //1.获取登录用户信息,例如用户id

                SysUser user=(SysUser)principals.getPrimaryPrincipal();

                Integer userId=user.getId();

                //2.基于用户id获取用户拥有的角色(sys_user_roles)

                List<Integer> roleIds=

                sysUserRoleDao.findRoleIdsByUserId(userId);

                if(roleIds==null||roleIds.size()==0)

                throw new AuthorizationException();

                //3.基于角色id获取菜单id(sys_role_menus)

                List<Integer> menuIds=

                sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);

            if(menuIds==null||menuIds.size()==0)

            throw new AuthorizationException();

                //4.基于菜单id获取权限标识(sys_menus)

            List<String> permissions=

            sysMenuDao.findPermissions(menuIds);

                //5.对权限标识信息进行封装并返回

            Set<String> set=new HashSet<>();

            for(String per:permissions){

                    if(!StringUtils.isEmpty(per)){

                            set.add(per);

                    }

            }

            SimpleAuthorizationInfo info=

            new SimpleAuthorizationInfo();

            info.setStringPermissions(set);

                return info;//返回给授权管理器

        }




}

1.6 Shiro记住我实现

1.6.1 服务端业务实现的具体步骤如下:

第一步:在SysUserController中的doLogin方法中基于是否选中记住我,设置token的setRememberMe方法.
@RequestMapping("doLogin")

         @ResponseBody

         public JsonResult doLogin(

                         boolean isRememberMe,

                         String username,

                         String password) {

                 //1.封装用户信息

                 UsernamePasswordToken token=

                 new UsernamePasswordToken(username, password);

                 if(isRememberMe) {

                        token.setRememberMe(true);

                 }

                 //2.提交用户信息

                 Subject subject=SecurityUtils.getSubject();

                 subject.login(token);//token会提交给securityManager

                 return new JsonResult("login ok");

         }
第二步: 在SpringShiroConfig配置类中添加记住我配置,关键代码如下:
@Bean

         public RememberMeManager rememberMeManager() {

                 CookieRememberMeManager cManager=

                 new CookieRememberMeManager();

  SimpleCookie cookie=new SimpleCookie("rememberMe");

                 cookie.setMaxAge(7*24*60*60);

                 cManager.setCookie(cookie);

                 return cManager;

         }
第三步: 在SpringShiroConfig中修改securityManager的配置,为securityManager注入rememberManager对象:
 @Bean

         public SecurityManager securityManager(

                        Realm realm,CacheManager cacheManager

RememberMeManager rememberManager) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 sManager.setCacheManager(cacheManager);

                 sManager.setRememberMeManager(rememberManager);

                 return sManager;

         }
第四步: 修改shiro的过滤认证级别,将/=author修改为/=user:
@Bean

         public ShiroFilterFactoryBean shiroFilterFactory(

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //假如没有认证请求先访问此认证的url

                 sfBean.setLoginUrl("/doLoginUI");

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 map.put("/user/doLogin","anon");

                 map.put("/doLogout", "logout");//自动查LoginUrl

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","user");//authc

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }

1.7shiro会话时长配置

使用shiro框架实现认证操作,用户登陆成功会将用户信息写入到会话对象中,其默认时长为30分钟,假如需要对此进行配置,可以参考如下代码:
第一步: 在SpringShiroConfig类中,添加会话管理器配置:
@Bean  

public SessionManager sessionManager() {

                 DefaultWebSessionManager sManager=

                                 new DefaultWebSessionManager();

                 sManager.setGlobalSessionTimeout(60*60*1000);

                 return sManager;

}
第二步: 在SpringShiroConfig配置类中,对安全管理器 securityManager增加sessionManager值的注入:
@Bean

public SecurityManager securityManager(

                        Realm realm,CacheManager cacheManager,

RememberMeManager rememberManager,

SessionManager sessionManager) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 sManager.setCacheManager(cacheManager);

                 sManager.setRememberMeManager(rememberMeManager);

                 sManager.setSessionManager(sessionManager);

                 return sManager;

}
阅读 149
4 声望
3 粉丝
0 条评论
4 声望
3 粉丝
宣传栏