动吧旅游生态系统——用户管理模块——shiro权限认证分析

2022-07-27,,,,

访问网页需要验证登录、不同的用户访问同一资源有不同的权限等都可以通过shiro实现认证

1.shiro介绍

2.业务分析

2.1 访问资源流程

anon、authc等是一些默认的过滤器,而这些过滤器由shiroFilterFactory工厂提供,而该工厂可以由shiroFilterFactoryBean对象创建,所以在配置类中需要配置该bean对象。
过滤器在servlet之前执行,而springmvc集成了servlet,即在controller前对资源进行了拦截,拦截后会调用securityManager进行认证,该对象定义了一系列的认证授权方法,一旦遇到需要认证的请求就跳回登录页面
shiroFilterFactoryBean对象规定了哪些资源需要认证访问,哪些可以直接访问,而具体有没有认证需要交给securityManager去检测该请求有没有被认证

2.2 登录验证

登陆界面输入的用户信息controller可以拿到,通过subject传给securutyManager,Realm中可以获取用户信息

放行登录的contrller
调用doLogin后 security Manager会将token交给认证管理器,认证管理器再交给realm做具体认证

2.3 授权分析

Shiro授权基于spring原生aop实现
Security Manager交给权限管理器去进行授权

2.4使用缓存

当我们进行授权操作时,每次都会从数据库查询用户权限信息,为了提高授权性能,可以将用户权限信息查询出来以后进行缓存,下次授权时从缓存取数据即可。
CacheManager是管理cache的一个对象,底层是用的softHashmap()对象,软引用,表示内存不足,如果GC触发,容器中关联的对象可以被回收。

2.5shiro记住我

使用session Manager和cookie Manager
过滤器改成user,表明可以从浏览器的cookie获取用户信息,
设置cookie的超时时间,不设置默认是会话cookie,浏览器关闭即结束,再打开浏览器cookie无效。

添加url重写,避免客户端将cookie禁用,无法通过cookie记录该客户端信息,通过重写的url后缀可以识别是同一客户端。
使用shiro框架实现认证操作,用户登录成功会将用户信息写入到会话对象中,其默认时长为30分钟,session的超时时间从对该页面不做任何操作开始计算,到达时间相当于锁屏,会清空session,也就表明需要重新登录

3.代码呈现

UserController中调用登录方法,subject主体携带用户信息的token,交给security Manager,security Manager交给认证管理器,认证管理器再交给realm去认证,ream中token包含了用户信息

 @RequestMapping("doLogin")
    public JsonResult doLogin(String username,String password,boolean isRememberMe){
        //1.获取Subject对象
        Subject subject = SecurityUtils.getSubject();
        //2.通过Subject提交用户信息,交给shiro框架进行认证操作
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        token.setRememberMe(isRememberMe);
        subject.login(token);
        //1)token会传给shiro的SecurityManager
        //2)SecurityManager将token传递给认证管理器
        //3)认证管理器会将token传递给realm
        return new JsonResult("login ok");
    }

配置类 分析可知:需要security Manager、工厂对象

package com.cy.pj.common.config;


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.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//配置类——可以直接写在启动类中,交给spring管理的一个配置对象
public class SpringShiroConfig {

    @Bean//此注解描述的方法的返回值交给spring管理
    public SecurityManager securityManager(Realm realm,CacheManager cacheManager,
                                           RememberMeManager rememberMeManager,
                                           SessionManager sessionManager){
        DefaultWebSecurityManager sManager= new DefaultWebSecurityManager();
        sManager.setRealm(realm);
        sManager.setCacheManager(cacheManager);
        sManager.setRememberMeManager(rememberMeManager);
        sManager.setSessionManager(sessionManager);
        return sManager;
    }
    @Bean//设置一些访问规则——匿名访问的资源,认证的访问资源
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean sFBean = new ShiroFilterFactoryBean();
        sFBean.setSecurityManager(securityManager);//这个请求是否已认证交给securityManager去做
        sFBean.setLoginUrl("/doLoginUI");//放行登录页面
        sFBean.setSuccessUrl("/doIndexUI");
        //定义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");
        //除了匿名访问的资源,其它都要认证("authc")后访问
//        map.put("/**","authc");
        map.put("/**","user");//记住我
        sFBean.setFilterChainDefinitionMap(map);
        return sFBean;
    }

    @Bean
    public CacheManager shiroCacheManager(){
        //具体使用的是softHashmap,软引用cache,好处在于内存不够时淘汰缓存中的数据
        return new MemoryConstrainedCacheManager();
    }
    @Bean
    public RememberMeManager cookieRememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setMaxAge(7*24*60*60);//7//不设置时间相当于临时cookie,浏览器关闭cookie生命周期结束,设置生命周期,关闭浏览器cookie信息还存在
        cookieRememberMeManager.setCookie(cookie);
        return cookieRememberMeManager;
    }

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sManager= new DefaultWebSessionManager();
        sManager.setGlobalSessionTimeout(60*60*1000);//60分钟
        sManager.setSessionIdUrlRewritingEnabled(false);//不启用url重写,
        return sManager;
    }

}

realm相当于service实现类,去做具体的认证和授权

package com.cy.pj.sys.service.realm;


import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysUser;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
public class ShiroUserRealm extends AuthorizingRealm {
    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private SysMenuDao sysMenuDao;
    @Autowired
    private SysUserRoleDao sysUserRoleDao;
    @Autowired
    private SysRoleMenuDao sysRoleMenuDao;

    @Override//重写get或set方法都可以——凭证匹配器,返回密码匹配对象——解密方式
    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);
       return cMatcher;
    }*/



    @Override
    //权限认证——查询角色——查询对应的菜单   ——用aop,在方法上自定义注解,根据方法上的注解和用户拥有的权限是否一致
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SysUser user=(SysUser)principals.getPrimaryPrincipal();//获取用户身份,和登录时封装的对应
        Integer userId = user.getId();
        //基于用户Id查询对应角色
        List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(userId);
        if(roleIds==null||roleIds.size()==0)
            throw new AuthorizationException();
        //基于角色Ids查询对应菜单
        List<Integer> menuIds = sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);
        if (menuIds==null||menuIds.size()==0)
            throw new AuthorizationException();
        //基于菜单Ids获取permission
        List<String> permissions = sysMenuDao.findPermissions(menuIds);
        //对权限信息进行封装——封装为set是因为下面的方法setStringPermissions()中的形参需要set类型的参数
        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;
    }

    @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());//将盐值转换成credentialsSalt
        //principal凭证/身份,hashedCredentials加密的密码,credentialsSalt凭证盐,realmName
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, this.getName());
        //6.返回封装结果
        return info;//返回值会传递给认证管理器,认证管理器会通过此信息完成认证操作
    }
}

本文地址:https://blog.csdn.net/cassiel_yuan/article/details/109635372

《动吧旅游生态系统——用户管理模块——shiro权限认证分析.doc》

下载本文的Word格式文档,以方便收藏与打印。