Shiro框架

1. Shiro概述

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

2. Shiro流程

在这里插入图片描述

3. Shrio架构

3.1 主要架构

在概念层面,Shiro 架构包含三个主要的理念
在这里插入图片描述
其中:
1)Subject :主体对象,负责提交用户认证和授权信息。
2)SecurityManager:安全管理器,负责认证,授权等业务实现。
3)Realm:领域对象,负责从数据层获取业务数据。

3.2 详细架构

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

4. Shiro拦截实现

4.1 添加shiro依赖

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

4.2 Shiro核心对象配置

基于SpringBoot 实现的项目中,我们的shiro应用基本配置如下:。
第一步:创建SpringShiroConfig类

package com.cy.pj.common.config;
/**@Configuration 注解描述的类为一个配置对象,
 * 此对象也会交给spring管理
 */
@Configuration
public class SpringShiroConfig {

}

第二步:在Shiro配置类中添加SecurityManager配置(这里一定要使用org.apache.shiro.mgt.SecurityManager这个接口对象)

 /**
     * 构建并初始化SecurityManager对象,然后将此对象交给spring管理.
     * 说明:@Bean注解应用于@Configuration注解描述的类的内部,通过此注解描述的方法,
     * 方法的返回值会交给spring管理,默认bean的名字为方法名.
     * @return 返回值shiro中的安全管理器对象,是shiro框架的核心,此对象中实现了
     * 认证,授权,会话,缓存,加密等一列功能的实现.
     */
       @Bean //bean的名字默认为方法名
       //@Scope("singleton")//默认
       public SecurityManager securityManager(){
           DefaultWebSecurityManager sManager=new DefaultWebSecurityManager();
           return sManager;
       }

第三步: 在Shiro配置类中添加ShiroFilterFactoryBean对象的配置。通过此对象设置资源匿名访问、认证访问

/**构建并初始化ShiroFilterFactoryBean对象通过此对象,
        * 创建过滤器工厂,进而通过过滤器工厂创建过滤器(filter),
        * 并通过过滤器对请求信息进行过滤,例如检测此请求是否需要
        * 认证或此请求是否已认证.
        * */
       @Bean
       //@Autowired //可以省略
       public ShiroFilterFactoryBean shiroFilterFactoryBean(
               SecurityManager securityManager){
           ShiroFilterFactoryBean fBean=new ShiroFilterFactoryBean();
           //设置securityManager,基于此对象进行认证检测
           fBean.setSecurityManager(securityManager);
           sfBean.setLoginUrl("/doLoginUI");//拦截后,跳转到登录页面
           //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
           LinkedHashMap<String,String> map= new LinkedHashMap<>();
           //静态资源允许匿名访问:"anon"
           map.put("/bower_components/**","anon");//AnonFilter
           map.put("/build/**","anon");
           map.put("/dist/**","anon");
           map.put("/plugins/**","anon");
           //除了匿名访问的资源,其它都要认证("authc")后访问
           map.put("/**","authc");//这句话要写在匿名访问的后面(有顺序要求)
           fBean.setFilterChainDefinitionMap(map);
           return fBean;
       }

其配置关系图:
在这里插入图片描述

5. Shiro认证实现

5.1 认证流程分析

身份认证即判定用户是否是系统的合法用户,用户访问系统资源时的认证(对用户身份信息的认证)流程图
在这里插入图片描述
其中认证流程分析如下:
1)系统调用subject的login方法将用户信息提交给SecurityManager
2)SecurityManager将认证操作委托给认证器对象Authenticator
3)Authenticator将用户输入的身份信息传递给Realm。
4)Realm访问数据库获取用户信息然后对信息进行封装并返回。
5)Authenticator 对realm返回的信息进行身份认证。

5.2 认证业务实现

1)在SysUserDao接口中,添加根据用户名获取用户对象的方法

@Select("select * from sys_users where username=#{username}")
SysUser findUserByUserName(String username)
  1. Realm业务
    我们编写realm时,要继承
    AuthorizingRealm并重写相关方法,完成认证及授权业务数据的获取及封装
@Component
public class ShiroUserRealm extends AuthorizingRealm {
    @Autowired
    private SysUserDao sysUserDao;
    
    /**负责用户权限信息的获取和封装*/
     @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
       		PrincipalCollection principalCollection) {
       return null;
     }
/**负责认证信息的获取和封装*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取登录用户信息(用户名,密码)
        UsernamePasswordToken token=
                (UsernamePasswordToken)authenticationToken;
        String username=token.getUsername();
        //2.基于用户名查询数据库中的用户对象
        SysUser user=sysUserDao.findUserByUserName(username);
        //3.检测用户是否存在
        if(user==null)
            throw new UnknownAccountException();
        //4.检测用户是否被禁用
        if(user.getValid()==0)
            throw new LockedAccountException();
        //5.封装用户信息并返回
        ByteSource byteSource=ByteSource.Util.bytes(user.getSalt());
        SimpleAuthenticationInfo info=
                new SimpleAuthenticationInfo(
                        user,//principal (用户身份)
                        user.getPassword(),//credentials (凭证-已加密密码)
                        byteSource,//credentialsSalt(凭证盐)
                        getName());
        return info;
    }
    /**设置加密匹配器**/
//    @Override
//    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
//        HashedCredentialsMatcher cMatcher=new HashedCredentialsMatcher();
//        cMatcher.setHashAlgorithmName("MD5");
//        cMatcher.setHashIterations(1);
//        super.setCredentialsMatcher(cMatcher);
//    }

    @Override
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher cMatcher=new HashedCredentialsMatcher();
        cMatcher.setHashAlgorithmName("MD5");
        cMatcher.setHashIterations(1);
        //cMatcher.setStoredCredentialsHexEncoded(true);
        return cMatcher;
    }
}

3)对此realm,需要在SpringShiroConfig配置类中,注入给SecurityManager对象,修改securityManager方法

@Bean
public SecurityManager securityManager(Realm realm) {
		 DefaultWebSecurityManager sManager=new DefaultWebSecurityManager();
		 sManager.setRealm(realm);
		 return sManager;
}

4)处理客户端的登陆请求,例如获取用户名,密码等然后提交该shiro框架进行认证

@RequestMapping("doLogin")
    public JsonResult doLogin(String username,String password){
   		 //1.获取Subject对象
        Subject subject= SecurityUtils.getSubject();
        //2.通过Subject提交用户信息,交给shiro框架进行认证操作
		   //2.1对用户进行封装
        UsernamePasswordToken token=new UsernamePasswordToken();
        token.setUsername(username);
        token.setPassword(password.toCharArray());
        //2.2对用户信息进行身份认证
        subject.login(token);//提交给securityManager
        return new JsonResult("login ok");
    }

5)修改shiroFilterFactory的配置,对/user/doLogin这个路径进行匿名访问的配置
map.put("/user/doLogin",“anon”);

@Bean
       //@Autowired //可以省略
       public ShiroFilterFactoryBean shiroFilterFactoryBean(
               SecurityManager securityManager){
           ShiroFilterFactoryBean fBean=new ShiroFilterFactoryBean();
           //设置securityManager,基于此对象进行认证检测
           fBean.setSecurityManager(securityManager);
           sfBean.setLoginUrl("/doLoginUI");//拦截后,跳转到登录页面
           //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
           LinkedHashMap<String,String> map= new LinkedHashMap<>();
           //静态资源允许匿名访问:"anon"
           map.put("/bower_components/**","anon");//AnonFilter
           map.put("/build/**","anon");
           map.put("/dist/**","anon");
           map.put("/plugins/**","anon");
           
     	   map.put("/user/doLogin","anon");

           //除了匿名访问的资源,其它都要认证("authc")后访问
           map.put("/**","authc");//这句话要写在匿名访问的后面(有顺序要求)
           fBean.setFilterChainDefinitionMap(map);
           return fBean;
       }

6)当我们在执行登录操作时,为了提高用户体验,可对系统中的异常信息进行处理,在统一异常处理类中添加

@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;
	}

6. Shiro退出实现

在shiroFilterFactory配置类中,修改过滤规则
添加 map.put("/doLogout",“logout”);

@Bean
       //@Autowired //可以省略
       public ShiroFilterFactoryBean shiroFilterFactoryBean(
               SecurityManager securityManager){
           ShiroFilterFactoryBean fBean=new ShiroFilterFactoryBean();
           //设置securityManager,基于此对象进行认证检测
           fBean.setSecurityManager(securityManager);
           sfBean.setLoginUrl("/doLoginUI");//拦截后,跳转到登录页面
           //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
           LinkedHashMap<String,String> map= new LinkedHashMap<>();
           //静态资源允许匿名访问:"anon"
           map.put("/bower_components/**","anon");//AnonFilter
           map.put("/build/**","anon");
           map.put("/dist/**","anon");
           map.put("/plugins/**","anon");
           
     	   map.put("/user/doLogin","anon");
     	   
     	   map.put("/doLogout","logout");

           //除了匿名访问的资源,其它都要认证("authc")后访问
           map.put("/**","authc");//这句话要写在匿名访问的后面(有顺序要求)
           fBean.setFilterChainDefinitionMap(map);
           return fBean;
       }

7. Shiro授权实现

7.1 授权流程

在这里插入图片描述
其中授权流程分析如下:
1)系统调用subject相关方法将用户信息(例如isPermitted)递交给SecurityManager。
2)SecurityManager将权限检测操作委托给Authorizer对象。
3)Authorizer将用户信息委托给realm。
4)Realm访问数据库获取用户权限信息并封装。
5)Authorizer对用户授权信息进行判定。

7.2 添加授权配置

 /** 配置授权对应的Advisor对象,此对象会在spring启动时加载,并且通过此
      * 对象可以找到@RequiresPermissions注解描述的方法,然后这些方法在运
      * 行时,由此Advisor对象,调用SecurityManager中的checkPermissions方法
      * 检查用户权限,并为访问目标切入点方法的用户做授权操作.
      * */
     @Bean
     public AuthorizationAttributeSourceAdvisor
           authorizationAttributeSourceAdvisor(SecurityManager securityManager){
           AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
           advisor.setSecurityManager(securityManager);
           return advisor;
     }

7.3 授权业务实现

@Component
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 principalCollection) {
        System.out.println("==doGetAuthorizationInfo==");
        //1.获取登录用户
        SysUser user=(SysUser) principalCollection.getPrimaryPrincipal();
        //2.基于登录用户查询用户对应的权限标识
        List<String> perimssionList=
                sysMenuDao.findUserPermissions(user.getId());
        if(perimssionList==null||perimssionList.size()==0)
            throw new AuthorizationException();
        //3.封装用户权限信息并返回(交给SecurityManager对象)
        Set<String> set=new HashSet<>();
        for(String per:perimssionList){
            if(!StringUtils.isEmpty(per)){
                set.add(per);
            }
        }
        System.out.println("set="+set);
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.setStringPermissions(set);
        return info;
    }
//基于单表查询
//    @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查找菜单id并校验
//        List<Integer>  menuIds=sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);
//        if(menuIds==null||menuIds.size()==0)
//            throw new AuthorizationException();
//        //4.基于菜单id找到授权标识并校验
//        List<String> perimssionList=sysMenuDao.findPermissions(menuIds);
//        if(perimssionList==null||perimssionList.size()==0)
//            throw new AuthorizationException();
//        //5.封装用户权限信息并返回(交给SecurityManager对象)
//        Set<String> set=new HashSet<>();
//        for(String per:perimssionList){
//            if(!StringUtils.isEmpty(per)){
//                set.add(per);
//            }
//        }
//        System.out.println("set="+set);
//        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//        info.setStringPermissions(set);
//        return info;
//    }
}

多表sql

<!--基于用户id进行多表查询获取用户权限-->
      <select id="findUserPermissions" resultType="string">
          select m.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}
      </select>

单表sql

<!--基于多个菜单id找到对应的权限标识-->
      <select id="findPermissions" resultType="string">
          select permission
          from sys_menus
          where id in <!--(1,2,3,4)-->
          <foreach collection="menuIds" open="(" close=")" separator="," item="menuId">
              #{menuId}
          </foreach>
       </select>

7.4 授权注解

@RequiresPermissions(“sys:user:update”)//注解中的字符串为一个权限标识

8.Shiro缓存配置

当进行授权操作时,每次都会从数据库查询用户权限信息,为了提高授权性能,可以将用户权限信息查询出来以后进行缓存,下次授权时从缓存取数据即可

/**
     * 配置CacheManager对象,此对象中管理着一个Cache对象,此cache可以
     * 存储授权时获取的用户权限信息,下次在授权时可以直接从缓存取用户权限,
     * 这样可以减少对数据库的访问压力,并提高其授权性能.
     * FAQ?我们知道此管理器内置一个Cache对象,那请问谁来调用此管理器并获取cache呢?
     * SecurityManager (因为此对象负责授权,授权就需要获取用户权限.)
     * @return
     */
     @Bean
     public CacheManager shiroCacheManager(){
        return new MemoryConstrainedCacheManager();
     }

将缓存对象注入给SecurityManager对象

@Bean //bean的名字默认为方法名
       //@Scope("singleton")//默认
       public SecurityManager securityManager(Realm realm,
                                              CacheManager cacheManager){
           DefaultWebSecurityManager securityManager=
                   new DefaultWebSecurityManager();
           securityManager.setRealm(realm);
           securityManager.setCacheManager(cacheManager);
           return securityManager;
       }

9.Shiro记住我功能

记住我功能是要在用户登录成功以后,假如关闭浏览器,下次再访问系统资源时,无需再执行登录操作
在SysUserController中的doLogin方法中基于是否选中记住我,设置token的setRememberMe方法
if(isRememberMe) {
token.setRememberMe(true);
}

@RequestMapping("doLogin")
    public JsonResult doLogin(String username,String password,boolean isRememberMe){
        Subject subject= SecurityUtils.getSubject();
        UsernamePasswordToken token=new UsernamePasswordToken();
        token.setUsername(username);
        token.setPassword(password.toCharArray());
        if(isRememberMe)
        token.setRememberMe(true);
        subject.login(token);//提交给securityManager
        return new JsonResult("login ok");
    }

添加记住我配置

    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager rManager=new CookieRememberMeManager();
        SimpleCookie cookie=new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);
        //假如没有设置maxAge,此cookie对象为会话cookie,此cookie会在浏览器关闭时生命周期结束
        rManager.setCookie(cookie);
        return rManager;
    }

将记住我对象注入给SecurityManager对象

@Bean //bean的名字默认为方法名
       //@Scope("singleton")//默认
       public SecurityManager securityManager(Realm realm,
                                              CacheManager cacheManager,
                                              RememberMeManager rememberMeManager,
                                              SessionManager sessionManager){
           DefaultWebSecurityManager securityManager=
                   new DefaultWebSecurityManager();
           securityManager.setRealm(realm);
           securityManager.setCacheManager(cacheManager);
           securityManager.setRememberMeManager(rememberMeManager);
           return securityManager;
       }

修改shiro的过滤认证级别

			//除了匿名访问的资源,其它都要认证("authc")后访问
           //map.put("/**","authc");//这句话要写在匿名访问的后面(有顺序要求)
           map.put("/**","user");//可以从用户浏览器cookie中读取账号信息进行身份认证

10.Shiro会话管理

使用shiro框架实现认证操作,用户登录成功会将用户信息写入到会话对象中,其默认时长为30分钟

添加会话管理配置

/**
     * Session 是什么?
     * 此对象是在服务端记录客户端与服务端会话状态的一个对象,
     * 这个对象一般是一个会话创建一个,并且会有一个唯一标识(JSESSIONID).
     * 可以通过这样的对象来记录登录用户信息,记录购物车信息,记录验证码信息
     * SessionManager 是什么?管理session的一个对象
     * @return
     */
    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(60*60*1000);//设置超时时间为1个小时
        //session对象由谁服务端创建,
        //但这个对象创建好以后,会将其jsessionid以会话cookie的形式写到客户端
        //客户端再访问服务器时,会携带jsessionid到服务端,然后基于这个id找到session.
        sessionManager.setSessionIdUrlRewritingEnabled(false);//关闭url重写.
        //一般假如浏览器禁用了cookie,我们可重写url,
        //此时会在url的后面添加一个jsessionid,服务端可以基于这个id为客户端找到对应session
        return sessionManager;
    }

将会话对象注入给SecurityManager对象

@Bean //bean的名字默认为方法名
       //@Scope("singleton")//默认
       public SecurityManager securityManager(Realm realm,
                                              CacheManager cacheManager,
                                              RememberMeManager rememberMeManager,
                                              SessionManager sessionManager){
           DefaultWebSecurityManager securityManager=
                   new DefaultWebSecurityManager();
           securityManager.setRealm(realm);
           securityManager.setCacheManager(cacheManager);
           securityManager.setRememberMeManager(rememberMeManager);
           securityManager.setSessionManager(sessionManager);
           return securityManager;
       }

11.shiro在springboot中的改进

11.1 配置类编写

@Configuration //此注解描述的类为spring中的配置类
public class SpringShiroConfig {

    @Bean
    public Realm realm() {
      return new ShiroUserRealm();//自己实现的Realm类
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        LinkedHashMap<String,String> map=new LinkedHashMap<>();
        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");
        
        map.put("/**","user");
        chainDefinition.addPathDefinitions(map);
        return chainDefinition;
    }
    @Bean
    protected CacheManager shiroCacheManager() {
        return new MemoryConstrainedCacheManager();
    }
}

其余跳转路径、记住我等功能在配置文件中配置
例如:

shiro:
   loginUrl: /doLoginUI
   rememberMeManager:
     cookie:
       name: rememberMe
       domain: null
       path: null
       secure: false

具体配置参见官方文档配置说明
http://shiro.apache.org/spring-boot.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值