shiro框架 02使用shiro进行用户的认证和用户权限控制

本文详细介绍了Shiro框架的认证和授权流程,包括Subject、SecurityManager、Authenticator、Authorizer和Realm的角色。在认证流程中,重点分析了从用户登录到信息验证的步骤,涉及DAO、Mapper和自定义Realm的实现。在授权部分,讲解了如何根据用户权限控制资源访问,配置授权相关bean,并展示了Service层如何获取和封装用户权限信息。整个过程展示了Shiro在实际项目中的应用和技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

01.shiro框架的具体业务:
在这里插入图片描述
具体的内部结构:

在这里插入图片描述

Subject(主体):与软件交互的一个特定的实体(用户、第三方服务等)。
SecurityManager(安全管理器) :Shiro 的核心,用来协调管理组件工作。
其中的:
Authenticator(认证管理器):负责执行认证操作。
Authorizer(授权管理器):负责授权检测。
Realms(领域对象):是 shiro 和你的应用程序安全数据之间的桥梁。

02.shiro所有的认证流程分析
上一章节是说的过滤器filter的构造,这里是认证:

在这里插入图片描述
其中认证流程分析如下:
01.系统调用 subject 的 login 方法将用户信息提交给 SecurityManager
02.SecurityManager 将认证操作委托给认证器对象 Authenticator
03.Authenticator 将用户输入的身份信息传递给 Realm。
04.Realm 访问数据库获取用户信息然后对信息进行封装并返回。
05.Authenticator 对 realm 返回的信息进行身份认证。

类型一:用户身份认证,身份认证即判定用户是否是系统的合法用户,用户访问系统资源时的认证(对用户身份信息的认证)流程图:
在这里插入图片描述
01.DAO 接口定义
业务描述及设计实现。
在用户数据层对象 SysUserDao 中,按特定条件查询用户信息,并对其进行封装。
关键代码分析及实现。
在 SysUserDao 接口中,添加根据用户名获取用户对象的方法,关键代码如下:

SysUser findUserByUserName(String username);

02.Mapper 元素定义
业务描述及设计实现。
根据 SysUserDao 中定义的方法,在 SysUserMapper 文件中添加元素定义。
关键代码分析及实现。
基于用户名获取用户对象的方法,关键代码如下:

  <select id="findUserByUserName"
	           resultType="com.cy.pj.sys.entity.SysUser">
	      select *
	      from sys_users  
	      where username=#{username}
	   </select>

03.Service 接口及实现
业务描述及设计实现。
本模块的业务在 Realm 类型的对象中进行实现,我们编写 realm 时,要继承
AuthorizingRealm 并重写相关方法,完成认证及授权业务数据的获取及封装。
关键代码分析及实现。
第一步:定义 ShiroUserRealm 类,关键代码如下:

package com.cy.pj.sys.service.realm;
	@Service
	public class ShiroUserRealm extends AuthorizingRealm {
	
	        @Autowired
	        private SysUserDao sysUserDao;
	                
	        /**
	         *因为用户的密码是经过加密得到的,所以验证的时候也需要将输入的验证的密码加密后与数据库中的进行比较
	         * 设置凭证匹配器(与用户添加操作使用相同的加密算法)告诉shiro我们使用什么加密算法
	         */
	        @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:通常情况是使用username与password来创建token(令牌)
	                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;
	}

04.Controller 类实现
业务描述及设计实现。
在此对象中定义相关方法,处理客户端的登陆请求,例如获取用户名,密码等然后提交该 shiro 框架进行认证。
关键代码分析及实现。
第一步:在 SysUserController 中添加处理登陆的方法。关键代码如下:

 @RequestMapping("doLogin")
	           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方法进行提交的
	                   subject.login(token);
	                   //分析:
	                   //1)token 会传给 shiro 的 SecurityManager
	                   //2)SecurityManager 将 token 传递给认证管理器
	                   //3)认证管理器会将 token 传递给 realm
	                   return new JsonResult("login ok");
	           }

UsernamePasswordToken:通常情况是使用username与password来创建token(令牌)
其中的
username也叫作身份:principals
password叫作凭证:credentials

第二步:修改 shiroFilterFactory 的配置,对/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) //shiro框架
@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; 	
	 	 	 	 	 	    }

instanceof
instanceof是Java的一个保留关键字,左边是对象,右边是类,返回类型是Boolean类型。它的具体作用是测试左边的对象是否是右边类或者该类的子类创建的实例对象,是,则返回true,否则返回false。

客户端的具体代码:
login的ajax请求代码:

$(function () {
	    $(".login-box-body").on("click",".btn",doLogin);
	  });
	  function doLogin(){
	          var params={
	                 username:$("#usernameId").val(),
	                 password:$("#passwordId").val()
	          }
	          var url="user/doLogin";
	          $.post(url,params,function(result){
	                  if(result.state==1){
	                        //跳转到 indexUI 对应的页面
	                        location.href="doIndexUI?t="+Math.random();
	                  }else{
	                        $(".login-box-msg").html(result.message);
	                  }
	          });
	  }

03.shiro的用户权限控制
授权即对用户资源访问的授权(是否允许用户访问此资源),用户访问系统资源时的授权流程如所示:
在这里插入图片描述
其中授权流程分析如下:
01.系统调用 subject 相关方法将用户信息(例如 isPermitted)递交给 SecurityManager。
02.SecurityManager 将权限检测操作委托给 Authorizer 对象。
03.Authorizer 将用户信息委托给 realm。
04.Realm 访问数据库获取用户权限信息并封装。
05.Authorizer 对用户授权信息进行判定。
06.添加授权配置
在 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;	
 	               }

核心业务分析 授权时,服务端核心业务以及 API 分析:
在这里插入图片描述
01.Dao
实现业务描述及设计实现。
基于登陆用户 ID,认证信息获取登陆用户的权限信息,并进行封装。
关键代码分析及实现。
第一步:在 SysUserRoleDao 中定义基于用户 id 查找角色 id 的方法(假如方法已经存在则无需再写),关键代码如下:

 List<Integer> findRoleIdsByUserId(Integer id);

第二步:在 SysRoleMenuDao 中定义基于角色 id 查找菜单 id 的方法,关键代码如下:

 List<Integer> findMenuIdsByRoleIds( @Param("roleIds")  Long[]  roleIds);

第三步:在 SysMenuDao 中基于菜单 id 查找权限标识的方法,关键代码如下:

 List<String> findPermissions(  @Param("menuIds")	   Integer[] menuIds);

02.Mapper
实现业务描述及设计实现。
基于 Dao 中方法,定义映射元素。关键代码分析及实现。
第一步:在 SysUserRoleMapper 中定义 findRoleIdsByUserId 元素。关键代码如下:

 <select id="findRoleIdsByUserId"	   resultType="long">	  
          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 中定义 findPermissions 元素,关键代码如下:

<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>

03.Service 实现业务描述及设计实现。
在 ShiroUserReam 类中,重写对象 realm 的 doGetAuthorizationInfo 方法,并完成用户权限信息的获取以及封装,最后将信息传递给授权管理器完成授权操作。关键代码分析及实现。
修改 ShiroUserRealm 类中的 doGetAuthorizationInfo 方法,关键代码如下:

@Service
public class ShiroUserRealm extends AuthorizingRealm { 
@Autowired 
      private SysUserMapper sysUserMapper;   
@Autowired  
  private SysUserRoleMapper sysUserRoleMapper;   
@Autowired    
private SysRoleMenuMapper sysRoleMenuMapper;
@Autowired  
  private SysMenuMapper sysMenuMapper;  
    /**通过此方法完成授权信息的获取及封装*/   
     @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {   
                  //1.获取登录用户信息,例如用户 id    
                 SysUser user=(SysUser)principals.getPrimaryPrincipal();             
                    Integer userId=user.getId();             
                 //2.基于用户 id 获取用户拥有的角色(sys_user_roles)                
                 List<Long> roleIds=  = sysUserRoleMapper.findRoleIdsByUserId(userId);               
                  if(roleIds==null||roleIds.size()==0)                    
                  throw new AuthorizationException();                
                  //3.基于角色 id 获取菜单 id(sys_role_menus)                
                  List<Integer> menuIds==sysRoleMenuMapper.findMenuIdsByRoleIds( roleIds.toArray(new Long[]{})); 
                                 if(menuIds==null||menuIds.size()==0)                    
                                 throw new AuthorizationException();                
                  //4.基于菜单 id 获取权限标识(sys_menus)                
                  List<String> permissions=sysMenuMapper.findPermissions(menuIds.toArray(new Integer[]{}));                
                  //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;//返回给授权管理器            
                  }  
                   //....}

授权访问实描述现
在需要进行授权访问的业务层(Service)方法上,添加执行此方法需要的权限标识,参考代码@RequiresPermissions(“sys:user:update”) 说明:此要注解一定要添加到业务层方法上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值