Shiro源码分析① :简单项目搭建

一、前言

由于之前没有使用过 Shiro,最近开始使用,故对其部分流程和源码进行了阅读,大体总结了一些内容记录下来。本系列并不会完完全全分析 Shiro 的全部代码,仅把主(我)要(用)流(到)程(的) 简单分析一下。由于本系列大部分为个人内容理解 并且 个人学艺实属不精,故难免出现 “冤假错乱”。如有发现,感谢指正,不胜感激。


Shiro 源码分析全集:

  1. Shiro源码分析① :简单项目搭建
  2. Shiro源码分析② :AbstractShiroFilter
  3. Shiro源码分析③ :认证流程
  4. Shiro源码分析④ :鉴权流程

本文会搭建一个最简单的Springboot + Shiro项目,目的在于为后续的Shiro 源码分析做准备,故不会详细介绍搭建过程。下面先介绍Shiro 中两个比较关键的类的实现。


1. SessionDao

在这里插入图片描述

  • SessionDAO :定义了从持久层操作session的标准;
  • AbstractSessionDAO :提供了SessionDAO的基础实现,如生成会话ID等;
  • CachingSessionDAO :提供了对开发者透明的session缓存的功能,只需要设置相应的 CacheManager 即可;
  • MemorySessionDAO :直接在内存中进行session维护;
  • EnterpriseCacheSessionDAO :提供了缓存功能的session维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。

2. SessionManager

  • DefaultSessionManager :JavaSE环境
  • ServletContainerSessionManager: Web环境,直接使用servlet容器会话。DefaultWebSecurityManager中默认使用的是ServletContainerSessionManager
  • DefaultWebSessionManager:用于Web环境的实现,可以替第二个,自己维护着会话,直接废弃了Servlet容器的会话管理

3. 各种过滤器

在这里插入图片描述

二、项目搭建

1. ShiroConfig

这是Shiro 的主要配置类

@Configuration
public class ShiroConfig {
    
    
    /**
     * 配置Shiro生命周期处理器。
     * 在 Bean创建时调用 init 方法,在 销毁时调用 destroy 方法
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    
    
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 定制的Realm,来完成认证和鉴权的部分功能
     *
     * @return
     */
    @Bean
    public CustomRealm customRealm() {
    
    
        return new CustomRealm();
    }

    /**
     * SecurityManager :核心类,具有极高的扩展性
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置定制的 Realm
        securityManager.setRealm(customRealm());
        // 配置 Session管理器
        securityManager.setSessionManager(sessionManager());
//        // 设置缓存管理器
//        securityManager.setCacheManager();
//        // 设置 RememberMeManager 来管理 RememberMeManager
//        securityManager.setRememberMeManager();

        return securityManager;
    }


    /**
     * 设置 ShiroFilter
     * 这里可以配置 Shiro 对接口的控制权限
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    
    
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 使用Map映射,key是接口路径,value为指定的Filter 名称,具体对应名称参考上图
        // 这里的配置是有顺序的,如果一个路径匹配多个过滤,那么只有先配置的过滤器生效
        Map<String, String> map = new HashMap<>();
        //配置登出接口
        map.put("/logout", "logout");
        // 放行登录接口
        map.put("/shiro/login", "anon");
        // 拦截其他接口
        map.put("/**", "authc");
        //设置登录路径,session失效会跳转该页面
        shiroFilterFactoryBean.setLoginUrl("/shiro/login");
        //登录成功页面
        shiroFilterFactoryBean.setSuccessUrl("/success");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 这里指定了动态代理的方式使用了 Cglib
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    
    
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        //        设置是否直接代理目标类,而不是仅代理特定的接口。 默认值为“ false”。
        //        将此设置为“ true”可强制代理TargetSource的公开目标类。 如果该目标类是接口,则将为给定接口创建一个JDK代理。 如果该目标类是任何其他类,则将为给定类创建CGLIB代理。
        //        注意:如果未指定接口(并且未激活接口自动检测),则根据具体代理工厂的配置,也会应用代理目标类行为
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * 开启注解支持,包括 RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class。
     * 和Aop相同的逻辑,通过注入 Advisor 来增强一些类的和方法
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    
    
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 在 DefaultWebSessionManager 情况下可以自定义Cookies 的一些信息
     * @return
     */
    public SimpleCookie sessionIdCookie() {
    
    
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("sid");
        //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:

        //setcookie()的第七个参数
        //设为true后,只能通过http访问,javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }

    /**
     * 设置SessionManager,由我们自己来控制Session
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
    
    
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        //配置监听
//        listeners.add(sessionListener());
//        sessionManager.setSessionListeners(listeners);
//        sessionManager.setSessionIdCookie(sessionIdCookie());
//        sessionManager.setSessionDAO(sessionDAO());
//        sessionManager.setCacheManager(ehCacheManager());

        //全局会话超时时间(单位毫秒),默认30分钟  暂时设置为10秒钟 用来测试
        sessionManager.setGlobalSessionTimeout(1800000);
        //是否开启删除无效的session对象  默认为true
        sessionManager.setDeleteInvalidSessions(true);
        //是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        //暂时设置为 5秒 用来测试
        sessionManager.setSessionValidationInterval(3600000);
        //取消url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

}

2. CustomRealm

CustomRealm 继承了AuthorizingRealm 类, 用来进行认证和鉴权的操作

public class CustomRealm extends AuthorizingRealm {
    
    

    /**
     * 权限认证
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        //获取登录用户名
        String name = (String) principalCollection.getPrimaryPrincipal();
        //查询用户名称
        User user = UserConstants.map.get(name);
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
    
    
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            //添加权限
            for (Permissions permissions : role.getPermissions()) {
    
    
                simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
            }
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 认证认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
    
    
            return null;
        }
        //获取用户信息
        String name = authenticationToken.getPrincipal().toString();
        User user = UserConstants.map.get(name);
        if (user == null) {
    
    
            //这里返回后会报出对应异常
            return null;
        } else {
    
    
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
            return simpleAuthenticationInfo;
        }
    }
}

3. ShiroDemoController

这里是测试用的控制层,为了简单,这里直接没有业务层,使用模拟数据

/**
 * @Author : wlhao
 * @Email : [email protected]
 * @Data: 2020/11/19 14:04
 * @Des:
 */
@RequestMapping("shiro")
@RestController
public class ShiroDemoController {
    
    

    @PostMapping("admin")
    // 需要admin角色,这里就说明只有 “张三”的账号才能访问
    @RequiresRoles("admin")
    public String admin() {
    
    
        return "admin";
    }

    @PostMapping("user")
    // 需要user角色, 这里只有“李四的账号才能访问”
    @RequiresRoles("user")
    public String user(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(true);
        return "user";
    }

    @PostMapping("login")
    public String login(HttpServletRequest request, HttpServletResponse response) {
    
    
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("张三", "123456");
        Subject subject = SecurityUtils.getSubject();
        subject.login(usernamePasswordToken);
        return "login";
    }
}

4. pojo 类

	@Data
	public class Permissions {
    
    
	    private String id;
	    private String permissionsName;
	}

	@Data
	
	public class Role {
    
    
	
	    private String id;
	    private String roleName;
	    /**
	     * 角色对应权限集合
	     */
	    private Set<Permissions> permissions;
	
	    public Role() {
    
    
	    }
	
	    public Role(String id, String roleName, Set<Permissions> permissions) {
    
    
	        this.id = id;
	        this.roleName = roleName;
	        this.permissions = permissions;
	    }
	}

	@Data
	
	public class User {
    
    
	    private String id;
	    private String userName;
	    private String password;
	    /**
	     * 用户对应的角色集合
	     */
	    private Set<Role> roles;
	
	    public User(String id, String userName, String password, Set<Role> roles) {
    
    
	        this.id = id;
	        this.userName = userName;
	        this.password = password;
	        this.roles = roles;
	    }
	
	    public User() {
    
    
	    }
	}

5. UserConstants

这里对User 用户进行数据模拟

/**
 * @Author : wlhao
 * @Email : [email protected]
 * @Data : 2021/1/28 10:54
 * @Desc : 模拟角色
 */
public class UserConstants {
    
    

    /**
     * 两个角色
     * 1. 张三 -》admin :具有 query,add 权限
     * 2. 李四 -》user : 具有 query 权限
     */
    public static Map<String, User> map = new HashMap<>();

    static {
    
    
        Permissions addPermissions = new Permissions("1", "query");
        Permissions queryPermissions = new Permissions("2", "add");

        Role adminRole = new Role("1", "admin", Sets.newHashSet(addPermissions, queryPermissions));
        Role userRole = new Role("2", "user", Sets.newHashSet(queryPermissions));

        User user = new User("1", "张三", "123456", Sets.newHashSet(adminRole));
        User user1 = new User("2", "李四", "123456", Sets.newHashSet(userRole));

        map.put(user.getUserName(), user);
        map.put(user1.getUserName(), user1);
    }

}

6. 异常拦截

全局异常拦截,方便看返回

@ControllerAdvice
@Slf4j
public class ShiroExceptionHandler {
    
    

    @ExceptionHandler
    @ResponseBody
    public String ErrorHandler(AuthorizationException e) {
    
    
        log.error("没有通过权限验证!", e);
        return "没有通过权限验证! " + e.getMessage();
    }
}

三、验证

当我们使用张三账号登录后,是无权访问user接口的,但是却可以访问 admin接口
在这里插入图片描述
在这里插入图片描述


以上:内容部分参考
https://blog.csdn.net/dgh112233/article/details/100083287
https://www.zhihu.com/pin/1105962164963282944
https://blog.csdn.net/qq_30643885/article/details/91886448
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/113310573