shiro框架的总结

Shiro安全框架

Shiro 是 apache 旗下一个开源安全框架,它对软件系统中的安全认证相关功能进行了封装,实现了用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

Shiro框架的详细架构

  • Subject : 主体对象,负责提交用户认证和授权信息。
  • SecurityManager(安全管理器) : Shiro 的核心,安全管理器,负责认证,授权等业务实现。
  • Authenticator(认证管理器):负责执行认证操作。
  • Authorizer(授权管理器):负责授权检测。
  • Cryptography(加密管理器):提供了加密方式的设计及管理
  • Realms(领域对象): 负责从数据层获取业务数据。

Shiro配置

1.下载依赖

<!--可在官网下载最新版本-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.0</version>
</dependency>

2.Shiro 核心对象配置

  1. 推荐在service的realm包下创建一个 Realm 类型的实现类(基于此类通过 DAO 访问数据库)
//定义shiro realm对象,基于此对象获取用户认证和授权信息,
//假如将来你的项目只做认证,不做授权,则继承继承AuthenticatingRealm对象即可
public class ShiroRealm extends AuthorizingRealm {
 /**此方法负责获取并封装授权信息*/
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(
 PrincipalCollection principalCollection) {
 return null;
 }
 /**此方法负责获取并封装认证信息*/
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(
 AuthenticationToken authenticationToken)
throws AuthenticationException {
 return null;
 }
}
  1. 在启动类同级下创建一个ShiroConfig配置类,添加 Realm 对象配置
//定义Realm对象,通过此对象访问数据库中的用户和权限信息,并进行封装。
//@Bean注解描述方法时,表示方法的返回要交给spring管理,这个bean的名字默认为方法名。
//还可以自己起名,例如@Bean("shiroRealm")*/
@Bean
public Realm realm() {
 return new ShiroRealm();
 }
  1. 在启动类中定义过滤规则(哪些访问路径要进行认证才可以访问)
Shiro验证URL时的顺序
  • URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
  • 故filterChainDefinitions的配置顺序为自上而下,以最上面的为准
user和authc都是认证过滤器,但不同的地方是:
  • 当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的。
//基于此对象定义过滤规则,例如哪些请求必须要认证,哪些请求不需要认证
//ShiroFilterChainDefinition 此对象中定义了若干过滤器Filter
//基于这些过滤器以及我们定义的过滤规则对业务进行实现。
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
 DefaultShiroFilterChainDefinition chainDefinition =
new DefaultShiroFilterChainDefinition();
 //设置登录操作不需要认证,anon表示可以匿名访问(不用登录就可以访问),
//其中 anon 为 shiro 框架指定的  匿名过滤器
chainDefinition.addPathDefinition("/user/login/**","anon");
//配置登出操作,登出以后要跳转到登录页面(其中,logout表示系统登出处理)
chainDefinition.addPathDefinition("/user/logout","logout");
//设置哪些资源需要认证才可访问
//其中 authc 为 shiro 框架指定的 认证过滤器
//chainDefinition.addPathDefinition("/**", "authc");
    
//当启用rememberme时,设置为user过滤器,因为authc是需要重新认证的。
chainDefinition.addPathDefinition("/**", "user");
 return chainDefinition;
}
  1. 在spring 的配置文件(application.yml)中,添加登录页面的配置
shiro:
  loginUrl: /login.html

Shiro认证业务实现

业务流程逻辑分析

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

业务逻辑实现

1.在 SysUserDao 中定义基于用户名查询用户信息的方法
@Select("select * from sys_users where username=#{username}")
SysUser selectUserByUsername(String username);
2.修改 ShiroRealm 中获取认证信息的方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken               authenticationToken) throws AuthenticationException {
    //1.获取客户端提交的用户名
    String username = ((UsernamePasswordToken)authenticationToken).getUsername();
    //2.基于用户名查询用户信息并校验
    SysUser sysUser = sysUserDao.selectUserByUsername(username);
    if(sysUser==null) throw new UnknownAccountException();
    if(sysUser.getValid()==0) throw new LockedAccountException();
    //3.封装用户信息
    ByteSource credentialsSalt = ByteSource.Util.bytes(sysUser.getSalt());
    String realmName= this.getName();
    //4.返回认证信息给Authenticator进行身份认证。
    return new SimpleAuthenticationInfo(
        sysUser,sysUser.getPassword(),credentialsSalt,realmName);
    }
3.在 ShiroRealm 中重谢获取凭证加密算法的方法
//重写此方法的目的是,底层对用户输入的登录密码进行加密,需要算法
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher =
                new HashedCredentialsMatcher("MD5");//MD5算法
        credentialsMatcher.setHashIterations(1);//加密次数1次
        return credentialsMatcher;
    }

4.在 SysUserController 中添加处理登录请求的方法

@GetMapping("/login/{username}/{password}")
public JsonResult doLogin(@PathVariable String username,
    @PathVariable String password){
     //将账号和密码封装 token 对象
     UsernamePasswordToken token = //参考官网
     new UsernamePasswordToken(username, password);
     //基于 subject 对象将 token 提交给 securityManager
    token.setRememberMe(true);//设置记住我
     Subject subject = SecurityUtils.getSubject();
     subject.login(token);//提交给 securityManager
     return new JsonResult("login ok");
}
5.处理异常:统一异常处理类中添加 shiro 异常处理代码
@ExceptionHandler(ShiroException.class)
    public JsonResult doShiroException(ShiroException e){
        JsonResult r=new JsonResult();
        r.setState(0);
        e.printStackTrace();
        log.error("exception {}",e.getMessage());
        if(e instanceof UnknownAccountException){
            r.setMessage("用户名不存在");
        }else if(e instanceof IncorrectCredentialsException){
            r.setMessage("密码不正确");
        }else if(e instanceof LockedAccountException){
            r.setMessage("账户被锁定");
        }else if(e instanceof AuthorizationException){
            r.setMessage("没有此权限");
        }else{
            r.setMessage("认证或授权失败");
        }
        return r;
    }
6.:在 spring 的配置文件(application.yml)中,添加登录页面的配置
shiro:
  loginUrl: /login.html

Shiro授权业务实现

业务流程逻辑分析

shiro授权业务流程图

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

业务逻辑实现

1.在 SysMenuDao 中定义基于用户名查询用户信息的方法
//用set集合接收permission字符串目的是去重,也可在sql语句中加入distinct
Set<String> selectUserPermissions(Integer userId);
2.在 SysMenuMapper 中添加查询用户权限标识的 SQL 映射
<select id="selectUserPermissions"
resultType="string">
select distinct permission
from sys_user_roles ur join sys_role_menus rm join sys_menus m
on ur.role_id=rm.role_id and rm.menu_id=m.id
where ur.user_id =# {userId} and m.permission!=''and m.permission is not null
</select>
3.修改 ShiroRealm 中获取权限并封装权限信息的方法
protected AuthorizationInfo doGetAuthorizationInfo(
    PrincipalCollection principalCollection) {
    //1.获取登录用户(登录时传入的用户身份是谁)
    SysUser user= (SysUser) principalCollection.getPrimaryPrincipal();
    //2.基于登录用户 id 获取用户权限标识
    Set<String> permissions=
    sysMenuDao.selectUserPermissions(user.getId());
    //3.封装用户权限信息并返回
    SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
    info.setStringPermissions(permissions);
    return info;
}
4.对需要授权登录的业务方法使用切入点注解描述
  • 在 shiro 框架中,授权切入点方法需要通过@RequiresPermissions 注解进行描述
  • //此注解的参数将会与用户的permission集合中的String对比
    //,如果有此参数则拥有访问此方法的权限。
    @RequiresPermissions("sys:user:update")
    public int validById(Integer id,Integer valid)

Shiro框架与其他API的不兼容

Shiro与PageHelper不兼容

不兼容案例:

  • 在Service层里使用shiro的@RequiresPermissions注解定义切入点方法。
  • 在Controller层中此方法使用了分页查询功能

结果:

  • 当客户端访问时,先通过Controller层调用Service层,而此时权限校验还未开启,
  • 所以当Service权限不通过时,页面上仍然会出现分页查询,但值为空。
  • 这是因为:SpringBoot先调用Controller层时不需要权限认证,分页查询仍然会被启动
解决方案1:在Service层启动分页拦截查询,Controller层调用此方法返回的结果
@RequiresPermissions("sys:user:view")
@Override
public PageInfo<SysUser> findUsers(SysUser sysUser) {
    return  PageUtil.startPage().doSelectPageInfo(()->{
    sysUserDao.selectUsers(sysUser);
    });
}
  • 这个方法的缺点就是,需要改动的代码较大,不仅要将Service层的方法返回值更换,还要更换抽象类和数据层的方法返回值
解决方案2:仍在Controller层中执行分页查询,但把@RequiresPermissions注解放在Controller的方法上
  • 如果把@RequiresPermissions注解放在Controller的方法上,此方法的其他注解会失效,这将导致GetMapper失效从而访问不到此url;
  • 这时可以在ShiroConfig配置类中配置此对象
//当将shiro中的注解RequiresPermissions放到控制层方法时需要配置此对象
//并设置对控制层方法上的注解有效(例如@GetMapping),即setUserPrefix为true
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
   DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=
       new DefaultAdvisorAutoProxyCreator();
   advisorAutoProxyCreator.setUsePrefix(true);
   return advisorAutoProxyCreator;
}

Shiro框架提供的其他功能

配置Session会话

  • 在ShrioConfig配置类中配置
@Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=
                new DefaultWebSessionManager();
        //session 超时自动关闭的时间
        sessionManager.setGlobalSessionTimeout(1000*60*60);//1 个小时
//        sessionManager.setGlobalSessionTimeout(2*60*1000);//2 分钟
        //删除无效 session
        sessionManager.setDeleteInvalidSessions(true);
        //当客户端 cookie 被禁用是否要设置 url 重写
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

配置"记住我"

记住我功能是要在用户登录成功以后,假如关闭浏览器,下次再访问系统资源时,无需再执行登录操作

"记住我"的本质是将Cookie持久化

  1. 在Controller的doLogin方法中对 UsernamePasswordToken token 对象进行配置:

    • token.setRememberMe(true);
  2. 在ShiroConfig配置类中配置

    • @Bean
          public RememberMeManager rememberMeManager(){
              CookieRememberMeManager cookieRememberMeManager =
              new CookieRememberMeManager();
              SimpleCookie cookie = new SimpleCookie("rememberMe");
              //将Cookie持久化
              cookie.setMaxAge(7*24*60*60);//设置为7天
              cookieRememberMeManager.setCookie(cookie);
              //当项目中通过shiro实现了记住我功能,假如此时客户端登录成功,服务端重启
              //再通过客户端访问,服务端就会出现问题,此时加入以下代码 
               cookieRememberMeManager.setCipherKey(Base64
                   .decode("FBenXrLADHpyCrwQ0+RI8Q=="));
              /*
              base64Encoded码在test中由以下代码生成
              @Test
              void testGeneratorKey() throws NoSuchAlgorithmException {
                  KeyGenerator keyGenerator= KeyGenerator.getInstance("AES");
                  SecretKey secretKey = keyGenerator.generateKey();
                  String key= Base64.encodeToString(secretKey.getEncoded());
                  System.out.println(key);
              }
               */
              return  cookieRememberMeManager;
          }
    • 当设置了记住我时有一个漏洞。当客户端用户登陆成功后,服务器重启会让客户端再次访问时出现错误

      • 解决方案:生成一个decode码 --- Base64.encodeToString(secretKey.getEncoded());
  3. 在Shiro配置类中修改认证拦截器中的过滤配置,将 authc 替换为 user

    • //当启用rememberme时,设置为user过滤器,因为authc是需要重新认证的
      chainDefinition.addPathDefinition("/**", "user");

授权缓存配置

Shiro框架默认客户端每次访问都会进行权限比对

但也给出了减少数据库的访问压力, 同时提高授权性能的对应方案

  • 在Shiro配置类中配置缓存

    @Bean
    protected CacheManager shiroCacheManager() {
       return new MemoryConstrainedCacheManager();
    }

配置好以后,重启服务,登陆成功以后,反复访问授权方法,检测 realm 中权限信息 的查询。


fubin
10 声望0 粉丝