动吧项目(权限管理子系统)学习-shiro框架

shiro安全框架

1.1 什么是shiro

Shiro 是 apache 旗下一个开源安全框架(http://shiro.apache.org/),它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。使用 shiro 就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

1.2 架构图

1.2.1 简要架构图

在这里插入图片描述

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

1.2.2 详细架构图

在这里插入图片描述

  1. Subject(主体):与软件交互的一个特定的实体(用户、第三方服务等)。
  2. SecurityManager(安全管理器) :Shiro 的核心,用来协调管理组件工作。
  3. Authenticator(认证管理器):负责执行认证操作。
  4. Authorizer(授权管理器):负责授权检测。
  5. SessionManager(会话管理):负责创建并管理用户 Session 生命周期,提供一
    个强有力的 Session 体验。
  6. SessionDAO:代表 SessionManager 执行 Session 持久(CRUD)动作,它允
    许任何存储的数据挂接到 session 管理基础上。
  7. CacheManager(缓存管理器):提供创建缓存实例和管理缓存生命周期的功能。
  8. Cryptography(加密管理器):提供了加密方式的设计及管理。
  9. Realms(领域对象):是 shiro 和你的应用程序安全数据之间的桥梁。

1.3 shiro框架认证拦截实现(Filter)

1.3.1 基本环境配置

添加依赖

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.7.0</version>
		</dependency>

1.3.2 核心对象配置

import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * @Configuration 注解描述的类,为springboot工程中的配置类,此类
 * 的实例由spring创建和管理。
 */
@Configuration
public class SpringShiroConfig {

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(2*1800000L);//默认为30分钟
        //不重写url
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
    /**记住我对象配置*/
    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager rememberMeManager=new CookieRememberMeManager();
        //构建Cookie对象,此对象负责存储用户状态信息,并将状态信息写到客户端
        SimpleCookie cookie=new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);//设置cookie的的生命周期
        rememberMeManager.setCookie(cookie);
        return rememberMeManager;
    }

    /**配置shiro缓存管理器,减少授权时,频繁访问数据库查询用户权限信息的过程。*/
    @Bean
    public CacheManager shiroCacheManager(){
        return new MemoryConstrainedCacheManager();
    }
    /**
     * 配置SecurityManager,此对象是Shiro框架的核心,负责认证和授权等业务实现。
     * 当由spring框架整合一个第三方的bean对象时,这个类型不是我们自己写的,
     * 我们无法在类上直接使用类似@Component的注解进行描述,那么如何去整合
     * 这样的bean的呢?
     * 解决方案:自己在spring中的配置类(@Configuration)中定义方法,
     * 在方法内部构建对象实例,并且由@Bean注解对方法进行描述即可.(记住,这是规则),
     * 这个注解描述的方法其返回值会交给spring管理,其bean的名字默认为方法名,
     * 当然也可以通过@Bean注解对应bean的名字进行定义(例如@Bean("sManager")).
     */
    //站在java多态特性应用的角度分析,方法的返回值,参数列表类型能用抽象尽量使用抽象类型。
    @Bean //
    public SecurityManager securityManager(Realm realm,
                                           CacheManager cacheManager,
                                           RememberMeManager rememberMeManager,
                                           SessionManager sessionManager){
        //在web应用项目中,SecurityManager的具体实现建议使用DefaultWebSecurityManager对象。
        DefaultWebSecurityManager securityManager=
                      new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(cacheManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    /**
     * 配置认证过滤规则,例如,哪些资源需要认证访问,哪些资源可以匿名访问。
     * 这个规则我们来定义,规则的检验在shiro框架中是借助大量过滤器(Filter)去实现的,
     * Shiro框架中提供了过滤器类型,但是基于其类型创建其过滤器实例需要通过滤器工厂,
     * 而我们这里配置的FactoryBean对象就是用于创建过滤器工厂的一个对象(spring框架中
     * 所有FactoryBean的作用都是用于创建过滤器工厂的)。
     * @return
     */
    @Bean
    //@Autowired 可以省略
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
        System.out.println("==shiroFilterFactoryBean===");
        ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
        //在map中存储规则,key为资源名,value为规则
        LinkedHashMap<String,String> map=new LinkedHashMap<>();
        map.put("/bower_components/**","anon");//anon表示匿名访问,Shiro框架定义的字符串
        map.put("/build/**","anon");
        map.put("/dist/**","anon");
        map.put("/plugins/**","anon");
        map.put("/user/doLogin", "anon");//放行登录操作,允许登录
        map.put("/doLogout", "logout");//当value为logout时,退出时会自动回到登录页面
        //除了以上资源,后续所有资源都要认证访问
        //map.put("/**", "authc");//authc表示需要认证
        map.put("/**", "user");//此方式的认证还可以从客户端的cookie中取用户信息
        filterFactoryBean.setFilterChainDefinitionMap(map);
        //如何判定你访问这个资源时是否已经认证过了呢?(要通过securityManager实现)
        filterFactoryBean.setSecurityManager(securityManager);
        //假如访问一个需要认证的资源,但这个用户还没有通过认证,我们要做什么?跳转到指定认证页面(例如登录页面)
        filterFactoryBean.setLoginUrl("/doLoginUI");
        return filterFactoryBean;
    }

    /**
     * 这里的advisor(顾问)负责找到类中使用此注解RequiresPermissions描述的方法,
     * 这些方法为授权访问的切入点方法,当在执行这些方时会由通知(Advice)对象
     * 调用SecurityManager对象完成权限检测及授权。
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}
1.3.3 编辑PageController类
@GetMapping("/doLoginUI")
    public String doLoginUI(){
        return "login";
    }
1.3.4 测试

在这里插入图片描述

1.4 登录认证实现

1.4.1 认证流程图

在这里插入图片描述

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

1.4.2 API流程图

在这里插入图片描述

1.4.3 编辑SysUserDao

/**
     * 基于用户名查找数据库中的用户对象
     * @param username
     * @return
     */
    @Select("select * from sys_users where username = #{username}")
    SysUser findUserByUsername(String username);

1.4.4 定义ShiroUserRealm类

/**
 * 定义realm类型继承授权AuthorizingRealm类型,
 * 假如只做认证可以直接继承认证AuthenticatingRealm即可.
 *
 * Shiro框架中Realm对象可以理解为用户获取认证数据信息和授权数据信息的一个对象。
 */
@Service
public class ShiroUserRealm extends AuthorizingRealm {//AuthorizingRealm 继承了 AuthenticatingRealm

    @Autowired
    private SysUserDao sysUserDao;
    
    /**
     * 此方法获取用户认证信息并进行封装
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取登录时输入的用户名
        UsernamePasswordToken upToken= (UsernamePasswordToken) authenticationToken;
        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.封装用户信息并返回,将信息交给SecurityManager进行认证
        ByteSource credentialSalt=ByteSource.Util.bytes(user.getSalt());
        SimpleAuthenticationInfo info=
                new SimpleAuthenticationInfo(user,//principal用户身份
                        user.getPassword(),//hashedCredentials已加密的密码
                        credentialSalt,//credentialSalt 加密盐
                        getName());//realmName
        return info;//此对象最终会交给securityManager
    }

    /***
     * SecurityManager比对密码时需要调用此方法获取加密算法相关信息
     * @return
     */
    @Override
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher=
                new HashedCredentialsMatcher("MD5");
        credentialsMatcher.setHashIterations(1);
        return credentialsMatcher;
    }

}

//login form->controller-->subject.login(token)-->securityManager-->realm-->dao

1.4.5 编辑SysUserController

@RequestMapping("/doLogin")
    public JsonResult doLogin(String username,String password,boolean isRememberMe){
        //将用户名和密码封装到令牌对象中
        UsernamePasswordToken token=
                new UsernamePasswordToken(username, password);
        Subject subject=SecurityUtils.getSubject();
        //设置记住我
        token.setRememberMe(isRememberMe);
        //提交用户信息进行登录
        subject.login(token);
        return new JsonResult("login ok");
    }
1.4.6 设置统一异常处理

在common项目中GlobalExceptionHandler类中编辑

/**
     * 处理shiro框架异常
     * @param e
     * @return
     */
    @ExceptionHandler(ShiroException.class)
    public JsonResult doHandleShiroException(ShiroException e){
        JsonResult result=new JsonResult();
        result.setState(0);//设置状态
        if(e instanceof UnknownAccountException){
            result.setMessage("账户不存在");
        }else if(e instanceof LockedAccountException){
            result.setMessage("账户已被禁用");
        }else if(e instanceof IncorrectCredentialsException){
            result.setMessage("账户密码不正确");
        }else if(e instanceof AuthorizationException){
            result.setMessage("没有此操作的权限");
        }else{
            result.setMessage("系统故障,请稍后访问");
        }
        e.printStackTrace();
        return result;
    }

1.5 授权实现

1.5.1 授权流程图

在这里插入图片描述

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

1.5.2 API流程图

在这里插入图片描述

1.5.3 编辑Dao

1.5.3.1 SysUserRoleDao
/**
     * 基于用户id查找用户对应的角色id
     * @param userId
     * @return
     */
    @Select("select role_id from sys_user_roles where user_id=#{userId}")
    List<Integer> findRoleIdsByUserId(Integer userId);
1.5.3.2 SysRoleMenuDao
List<Integer> findMenuIdsByRoleIds(@Param("roleIds")List<Integer> roleIds);
1.5.3.3 SysMenuDao
List<String> findPermissions(List<Integer> menuIds);

1.5.4 编辑Mapper.xml

1.5.4.1 SysRoleMenuMapper
    <select id="findMenuIdsByRoleIds" resultType="int">
        select menu_id
        from sys_role_menus
        where role_id in  <!--(1,2,3,4,5)-->
        <foreach collection="roleIds" open="(" close=")" separator="," item="roleId">
            #{roleId}
        </foreach>
    </select>
1.5.4.2 SysMenuMapper
    <select id="findPermissions" resultType="string">
         select permission
         from sys_menus
         where id in
         <foreach collection="menuIds" open="(" close=")" separator="," item="menuId">
             #{menuId}
         </foreach>
    </select>

1.5.4 编辑ShiroUserRealm类

    @Autowired
    private SysUserRoleDao sysUserRoleDao;
    @Autowired
    private SysRoleMenuDao sysRoleMenuDao;
    @Autowired
    private SysMenuDao sysMenuDao;
  /**
     *此方法用于获取用户的权限信息并进行封装。
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principalCollection) {
        //1.获取登录用户
        SysUser user=(SysUser)principalCollection.getPrimaryPrincipal();
        //2.获取登录用户的角色id (从用户角色关系表)
        List<Integer> roleIds=sysUserRoleDao.findRoleIdsByUserId(user.getId());
        if(roleIds==null||roleIds.size()==0)throw new AuthorizationException();
        //3.获取角色对应的菜单id(用角色菜单关系表)
        List<Integer> menuIds=sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);
        if(menuIds==null||menuIds.size()==0)throw new AuthorizationException();
        //4.获取菜单id对应授权标识(permisssion)-从菜单表
        List<String> permissions=sysMenuDao.findPermissions(menuIds);
        if(permissions==null||permissions.size()==0)throw new AuthorizationException();
        //5.封装数据并返回,交给securityManager对象
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        Set<String> setPermissions=new HashSet<>();
        for(String per:permissions){
            if(per!=null&&!"".equals(per)){
                setPermissions.add(per);
            }
        }
        System.out.println("setPermissions="+setPermissions);
        info.setStringPermissions(setPermissions);
        return info;
    }
        /**
     * 这里的advisor(顾问)负责找到类中使用此注解RequiresPermissions描述的方法,
     * 这些方法为授权访问的切入点方法,当在执行这些方时会由通知(Advice)对象
     * 调用SecurityManager对象完成权限检测及授权。
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

1.5.5 授权方法上添加注解

在需要进行授权访问的业务层(Service)方法上,添加执行此方法需要的权限标识,参考代码@RequiresPermissions(“sys:user:update”)

    /**
     *   @RequiresPermissions 注解描述的方法为授权访问切入点方法,表示
     * 访问这个方法需要授权.这个授权的动作由shiro框架中的securityManager
     * 对象来实现,请问这个对象如何对用户进行授权?第一,要获取用户有什么权限,
     * 第二,要获取访问这个方法需要什么权限(RequiresPermissions内部value属性
     * 的值) 第三,检测用户拥有的这些权限中是否包含执行此方法需要的权限,假如
     * 包含则授权。
     * @param id
     * @param valid
     * @return
     */
    @Transactional //此注解描述的方法为事务切入点方法
    @RequiresPermissions(value="sys:user:update")//述的方法为授权访问切入点方法
    @RequiredLog //此注解描述的方法为日志切入点方法
    @Override
    public int validById(Integer id, Integer valid) {
        ///...............
        //1.参数校验
        if(id==null||id<1)
            throw new IllegalArgumentException("id值无效");
        if(valid==null||valid!=0&&valid!=1)
            throw new IllegalArgumentException("状态值不正确");
        //2.修改状态并校验结果
        int rows=sysUserDao.validById(id,valid,"admin");//这里admin代表登录用户
        if(rows==0)
            throw new ServiceException("记录可能已经不存在");
        return rows;
    }

1.6 shiro中的缓存配置

1.6.1 背景分析

对于已认证用户而言,默认每次进行授权方法的访问,偶需要从数据库中查询用户的权限信息,而这部分信息相对比较稳定,但是频繁访问数据获取这部分信息可能会在性能上有一定的影响,如何解决这个问题?
– 借助缓存,将查询出来的数据暂时放到缓存中,下次授权可以从缓存中获取

1.6.2 添加配置

    /**配置shiro缓存管理器,减少授权时,频繁访问数据库查询用户权限信息的过程。*/
    @Bean
    public CacheManager shiroCacheManager(){
        return new MemoryConstrainedCacheManager();
    }
   
    //站在java多态特性应用的角度分析,方法的返回值,参数列表类型能用抽象尽量使用抽象类型。
    @Bean
    public SecurityManager securityManager(Realm realm,
                                           CacheManager cacheManager,
                                           RememberMeManager rememberMeManager,
                                           SessionManager sessionManager){
        //在web应用项目中,SecurityManager的具体实现建议使用DefaultWebSecurityManager对象。
        DefaultWebSecurityManager securityManager=
                      new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(cacheManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

1.7 “记住我” 功能实现

1.7.1 编辑SpringShiroConfig类

/**记住我对象配置*/
    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager rememberMeManager=new CookieRememberMeManager();
        //构建Cookie对象,此对象负责存储用户状态信息,并将状态信息写到客户端
        SimpleCookie cookie=new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);//设置cookie的的生命周期
        rememberMeManager.setCookie(cookie);
        return rememberMeManager;
    }

1.7 Shiro 会话管理配置

1.7.1 背景分析

web客户端与服务端通讯时可能会产生一些会话状态,这些状态信息无法通过http协议进行存储,因为此协议本生是一个无状态协议,该如何进行存储?

  • 在JavaEE中定义了两种对象存储
    Cookie(客户端记录)
    Session(服务端记录)

1.7.2 编辑 SpringShiroConfig 类

@Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(2*1800000L);//默认为30分钟
        //不重写url
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Solider

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值