Análisis del código fuente de Shiro①: Construcción simple del proyecto

I. Introducción

Como no he usado Shiro antes, comencé a usarlo recientemente, así que leí algunos de sus procesos y código fuente, resumí parte del contenido y lo grabé. Esta serie no analizará completamente todo el código de Shiro, sino que solo analizará brevemente el principal (I) necesito (uso) flujo (a) proceso (de) . Dado que la mayor parte de esta serie es para la comprensión personal y las habilidades de aprendizaje personal no son buenas, es inevitable que haya una "confusión injusta y falsa". Si encuentra algo, gracias por sus correcciones, estoy muy agradecido.


Trabajos completos de análisis de código fuente de Shiro:

  1. Análisis del código fuente de Shiro①: Construcción simple del proyecto
  2. Análisis del código fuente de Shiro ②: AbstractShiroFilter
  3. Análisis del código fuente de Shiro ③: proceso de certificación
  4. Análisis del código fuente de Shiro ④: proceso de autenticación

Este artículo construirá un proyecto Springboot + Shiro más simple, el propósito es preparar el análisis de seguimiento del código fuente de Shiro, por lo que el proceso de construcción no se presentará en detalle. Lo siguiente primero presenta la implementación de dos clases clave en Shiro.


1. SessionDao

Inserte la descripción de la imagen aquí

  • SessionDAO : define el estándar para la sesión operativa desde la capa de persistencia;
  • AbstractSessionDAO : proporciona la implementación básica de SessionDAO, como generar ID de sesión, etc .;
  • CachingSessionDAO : proporciona una función de caché de sesión transparente para los desarrolladores, solo es necesario configurar el CacheManager correspondiente;
  • MemorySessionDAO : Realice el mantenimiento de la sesión directamente en la memoria;
  • EnterpriseCacheSessionDAO : Proporciona mantenimiento de sesión para la función de caché. De forma predeterminada, se implementa mediante MapCache y ConcurrentHashMap se utiliza internamente para almacenar sesiones en caché.

2. SessionManager

  • DefaultSessionManager : JavaSE环境
  • ServletContainerSessionManager : entorno web, utilice directamente la sesión del contenedor de servlets. ServletContainerSessionManager se usa de forma predeterminada en DefaultWebSecurityManager
  • DefaultWebSessionManager : Para la implementación del entorno Web, puede mantener la sesión para el segundo y descartar directamente la gestión de sesiones del contenedor Servlet.

3. Varios filtros

Inserte la descripción de la imagen aquí

2. Construcción del proyecto

1. ShiroConfig

Esta es la clase de configuración principal de 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 hereda la clase AuthorizingRealm para las operaciones de autenticación y autenticación

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

Aquí está la capa de control para las pruebas. Para simplificar, no hay una capa comercial directamente aquí, y se utilizan datos simulados.

/**
 * @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. tipo 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

Simulación de datos para usuario usuario aquí

/**
 * @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. Intercepción de excepciones

Intercepción global de excepciones, fácil de ver y devolver

@ControllerAdvice
@Slf4j
public class ShiroExceptionHandler {
    
    

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

Tres, verificación

Cuando iniciamos sesión con la cuenta de Zhang San, no tenemos derecho a acceder a la interfaz de usuario, pero podemos acceder a la interfaz de administración.
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí


Arriba: Para ver el contenido, consulte
https://blog.csdn.net/dgh112233/article/details/100083287
https://www.zhihu.com/pin/1105962164963282944
https://blog.csdn.net/qq_30643885 / article / details / 91886448
Si hay alguna intrusión, contacta para eliminarla. El contenido solo se utiliza para la autograbación y el aprendizaje. Si hay algún error, corrígeme

Supongo que te gusta

Origin blog.csdn.net/qq_36882793/article/details/113310573
Recomendado
Clasificación