`
fenglin
  • 浏览: 31771 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Spring+Hibernate+Acegi

    博客分类:
  • Java
阅读更多
到现在我也没有弄明白Acegi里面很多的功能,刚刚开始学的时候我就已经被它那繁琐的配置震慑住了,不过当我动起手来一步步实现的时候,才发现其实它远没有那么难,当然随着学习的深入,会渐渐再发现这一点吧,现在就让我们初学者一切体验Acegi的功能吧!

还以我传统的例子为例:
毕业设计选题系统,三种角色:教师,学生,管理员,我想让他们的登陆都在一个界面下自动识别,而无需进行身份选择,登陆后,他们将分别到各自的admin.jsp,stu.jsp,teacher.jsp
在数据库中的表结构如下(很多属性略):
id--- user---password--type---about

type是用来存储用户的类别,分别有a,t,s分别对应三种角色
about对应的是acegi里所需要的enable,用户是否可用

在model里,我们采用了继承关系:

父类user:

package subject.model;   
  
public abstract class User extends BaseObject   
{   
 private Integer id;   
 private String user;   
 private String password;   
 private String name;   
 private String telphone;   
  
  //set and get method    
 //这个是用来反映用户角色的关键函数,在子类实现,从而实现多态   
 public abstract String getType();    
}   


子类的实现:
======================
package subject.model;   
  
import subject.Constants;   
  
public class Teacher extends User   
{   
 private String level;         //教师的职称   
  
//set and get method   
  
 public String getType()   
 {   
  return Constants.TEACHER;   
 }   
}   



================

package subject.model;   
  
import subject.Constants;   
  
public class Student extends User   
{   
 private static final long serialVersionUID = 1L;   
  
 private SchoolClass schoolClass;         //学生的班级   
 private String sn;             //学生的学号   
  
//set and get method   
    
 public String getType()   
 {   
  return Constants.STUDENT;   
 }   
}   



=================

package subject.model;   
  
import subject.Constants;   
  
public class Admin extends User   
{   
 private String grade;           //管理员的级别   
//set and get method   
  
 public String getType()   
 {   
  return Constants.ADMIN;   
 }   
}   


对于三者所共有的属性在数据库里,都存在一个字段,而依据不同的角色拥有不同的含义,学生的班级则存放在了about里,只要学生有班级,他就able,否则就enable了!而管理员和教师则默认为1!

这种是属于一个继承树存放在一个表的情况,Hibernate的配置如下:

<hibernate-mapping>  
  
 <class name="subject.model.User" discriminator-value="not null">  
  
  <id name="id">  
   <generator class="increment" />  
  </id>  
     
  <discriminator column="type" type="character" />  
     
  <property name="user" />  
  <property name="password" />  
  <property name="name" />  
  <property name="telphone" />  
  
  <subclass name="subject.model.Admin" discriminator-value="a">  
   <property name="grade" column="sn" />  
  </subclass>  
     
  <subclass name="subject.model.Teacher" discriminator-value="t">  
   <property name="level" column="sn" />  
  </subclass>  
     
  <subclass name="subject.model.Student" discriminator-value="s">  
      
   <property name="sn" />  
      
   <many-to-one name="schoolClass" class="subject.model.SchoolClass"    
    column="about" update="false" insert="false" />  
       
  </subclass>  
  
 </class>  
  
</hibernate-mapping>  


=============================================
上面的这些都是模型的基础,下面再讲怎么样配合Spring和Acegi实现系统的安全与登陆
在Spring中Hibernate的配置只介绍不说明:

<!-- 定义DBCP数据源 -->  
 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
  <property name="url" value="jdbc:mysql://localhost/subject?useUnicode=true&characterEncoding=gbk" />  
  <property name="username" value="root" />  
  <property name="password" value="" />  
  <property name="maxActive" value="100" />  
  <property name="maxIdle" value="30" />  
  <property name="maxWait" value="1000" />  
  <property name="defaultAutoCommit" value="true" />  
  <property name="removeAbandoned" value="true" />  
  <property name="removeAbandonedTimeout" value="60" />  
 </bean>  
  
 <!-- Hibernate -->  
 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
  <property name="dataSource" ref="dataSource" />  
  <property name="mappingResources">  
   <list>  
    <value>subject/model/User.hbm.xml</value>  
   </list>  
  </property>  
  <property name="hibernateProperties">  
   <props>  
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>  
   </props>  
  </property>  
 </bean>  
  
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
  <property name="sessionFactory" ref="sessionFactory" />  
 </bean>  
  
<!-- Dao对象 -->  
<bean id="userDao" class="subject.dao.hibernate.UserDaoImpl">  
  <property name="sessionFactory" ref="sessionFactory" />  
 </bean>  
  
<!-- 业务逻辑 -->  
 <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
  <property name="transactionManager" ref="transactionManager" />  
  <property name="transactionAttributes">  
   <props>  
    <prop key="save*">PROPAGATION_REQUIRED</prop>  
    <prop key="remove*">PROPAGATION_REQUIRED</prop>  
    <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>  
   </props>  
  </property>  
 </bean>  
  
<bean id="userManager" parent="txProxyTemplate">  
  <property name="target">  
   <bean class="subject.service.impl.UserManagerImpl">  
    <property name="userDao" ref="userDao" />  
   </bean>  
  </property>  
 </bean>  
  
<!-- Struts -->  
 <bean name="/user" class="subject.web.action.UserAction" singleton="false">  
  <property name="userManager">  
   <ref bean="userManager" />  
  </property>  
 </bean>  


==================
上面具体的不用了解,无非就是调用和数据库的操作,
下面就要对Acegi进行声明了:
我不用Ctrl+c和Ctrl+V的方式对Acegi进行介绍,没有意义,随便google就一大堆
我们想主要在这样的系统中需要的安全策略都有哪些?
1.用户的登陆
2.防止多个用户登陆一个帐号
3.用户的注销
4.防止非法用户的访问

我这个程序所涉及到的只有这些,下面就进行说明:

在web.xml的声明:

<!-- Acegi安全控制 Filter 配置 -->  
    <filter>  
        <filter-name>securityFilter</filter-name>  
        <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>  
        <init-param>  
            <param-name>targetClass</param-name>  
            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>  
        </init-param>  
    </filter>  
       
    <filter-mapping>  
        <filter-name>securityFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  

Acegi通过实现了Filter接口的FilterToBeanProxy提供一种特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy来完成过滤功能,这样就简化了web.xml的配置,并且利用Spring IOC的优势。FilterChainProxy包含了处理认证过程的filter列表,每个filter都有各自的功能。

<!-- ======================== FILTER CHAIN ======================= -->  
 <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">  
  <property name="filterInvocationDefinitionSource">  
   <value>  
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON    
    PATTERN_TYPE_APACHE_ANT   
       
    /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,   
         securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor   
   </value>  
  </property>  
 </bean>  



大体上先介绍一下:
httpSessionContextIntegrationFilter:每次request前 HttpSessionContextIntegrationFilter从Session中获取Authentication对象,在request完后, 又把Authentication对象保存到Session中供下次request使用,此filter必须其他Acegi filter前使用,使之能跨越多个请求。
logoutFilter:用户的注销
authenticationProcessingFilter:处理登陆请求
exceptionTranslationFilter:异常转换过滤器
filterInvocationInterceptor:在访问前进行权限检查

这些就犹如在web.xml声明一系列的过滤器,不过当把他们都声明在spring中就可以享受Spring给我们带来的方便了。

下面就是对这些过滤器的具体声明:
只对有用的地方进行声明,别的地方几乎都是默许的

<!-- ======================== FILTER ======================= -->  
 <bean id="httpSessionContextIntegrationFilter"    
  class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />  
  
 <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">  
  <constructor-arg value="/index.htm" />             离开后所转向的位置   
  <constructor-arg>  
            <list>  
                <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>  
            </list>  
        </constructor-arg>  
  <property name="filterProcessesUrl" value="/logout.htm" />        定义用户注销的地址,   
 </bean>  

 


下面的这个过滤器,我们根据自己的需求有了自己的实现:

 <bean id="authenticationProcessingFilter" class="subject.web.filter.UserAuthenticationProcessingFilter">  
  <property name="authenticationManager" ref="authenticationManager"/>  下面会介绍的用来起到认证管理的作用   
  <property name="authenticationFailureUrl" value="/login.htm?error=wrong"/>  登陆失败的地址   
  <property name="defaultTargetUrl" value="/login.htm"/>       登陆成功的地址   
  <property name="filterProcessesUrl" value="/j_security_check"/>      登陆请求的地址   
  <property name="userManager" ref="userManager"/>        自己添加的属性,这样就可以访问到我们的业务逻辑   
  <property name="exceptionMappings">   出现异常所对应的地址   
            <value>  
                org.acegisecurity.AuthenticationException=/login.htm?error=fail     登陆失败                org.acegisecurity.concurrent.ConcurrentLoginException=/login.htm?error=too        已登陆了   
            </value>  
        </property>  
 </bean>  
    
 <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>  
  
 <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">  
  <property name="authenticationEntryPoint">  
   <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">  
    <property name="loginFormUrl" value="/login.htm?error=please"/>//如果用户没登陆就想访问,先到这里登陆吧   
    <property name="forceHttps" value="false"/>  
   </bean>  
  </property>  
 </bean>  
    
 <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">  
  <property name="authenticationManager" ref="authenticationManager"/>       认证服务   
  <property name="accessDecisionManager">  
   <bean class="org.acegisecurity.vote.AffirmativeBased">  
    <property name="allowIfAllAbstainDecisions" value="false"/>  
    <property name="decisionVoters">  
     <list>  
      <bean class="org.acegisecurity.vote.RoleVoter">  
                    <property name="rolePrefix" value=""/>         //这里定义数据库中存放的角色和我们在这里声明的角色间是否需要加个前缀?我没加   
                </bean>  
     </list>  
    </property>  
   </bean>  
  </property>  
  <property name="objectDefinitionSource">  
            <value>  
                PATTERN_TYPE_APACHE_ANT   
                   
                /admin.htm*=a         这里就是数据库中对应的tyep a   
                /student*=s           由于没有前缀和数据库里一样   
                /teacher*=t   
            </value>  
        </property>  
 </bean>  
    
 <bean id="loggerListener"  
          class="org.acegisecurity.event.authentication.LoggerListener"/>       记录事件   

 
下面就要说明我们的认证服务了,其起到的关键作用就是用来保证用户登陆身份的验证:  
 
它将验证的功能委托给多个Provider,并通过遍历Providers, 以保证获取不同来源的身份认证,若某个Provider能成功确认当前用户的身份,authenticate()方法会返回一个完整的包含用户授权信息的Authentication对象,否则会抛出一个AuthenticationException。  
 
先声明一个管理器吧,在上面的过滤器中都已经用到过了  
<!-- ======================== AUTHENTICATION ======================= -->  
 <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">  
  <property name="providers">  
   <list>  
    <ref local="daoAuthenticationProvider" />   我仅仅用到 从数据库中读取用户信息验证身份   
   </list>  
  </property>  
  <property name="sessionController">  
   <bean id="concurrentSessionController"    
    class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">  
    <property name="maximumSessions">  
     <value>1</value>每个用户同时登陆一位   
    </property>  
    <property name="sessionRegistry">  
     <bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl" />  
    </property>  
    <property name="exceptionIfMaximumExceeded" value="true" />  
   </bean>  
  </property>  
 </bean>  
 来实现唯一的一个Provider,从数据库验证身份   
 <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">  
  <property name="userDetailsService">  
   <bean id="jdbcDaoImpl"  
            class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  
          <property name="dataSource" ref="dataSource"/>  
          <property name="usersByUsernameQuery">  
              <value>  
                  select user,password,about from user where user = ?        查找用户的查询语句,只需要把你数据库中的用户和密码以及enable相对应上就行   
              </value>  
          </property>  
          <property name="authoritiesByUsernameQuery">  
              <value>  
                  select user,type from user where user = ?           这里就是把用户和权限对应上,在appfuse中用的两个表,我都放一个表里了,所以就用这一个就行问题的关键是要让它能找到两个字段,构成一个对象   
              </value>  
          </property>  
      </bean>  
  </property>  
  <property name="userCache"> 缓存都这么写:   
   <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">  
    <property name="cache">  
     <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">  
      <property name="cacheManager">  
       <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>  
      </property>  
      <property name="cacheName" value="userCache"/>  
     </bean>  
    </property>  
   </bean>  
  </property>  
 </bean>  

==============
对于上面登陆请求的处理器我借鉴了springSide,实现的方法如下:

package subject.web.filter;   
  
import javax.servlet.http.HttpServletRequest;   
import javax.servlet.http.HttpServletResponse;   
import javax.servlet.http.HttpSession;   
  
import org.acegisecurity.Authentication;   
import org.acegisecurity.context.SecurityContext;   
import org.acegisecurity.context.SecurityContextHolder;   
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;   
import org.acegisecurity.userdetails.UserDetails;   
  
import subject.Constants;   
import subject.model.User;   
import subject.service.UserManager;   
  
public class UserAuthenticationProcessingFilter extends  
  AuthenticationProcessingFilter   
{   
 private UserManager userManager;   
  
 public void setUserManager( UserManager userManager )   
 {   
  this.userManager = userManager;   
 }   
  
 protected boolean requiresAuthentication( HttpServletRequest request ,   
   HttpServletResponse response )   
 {   
  boolean requiresAuth = super.requiresAuthentication( request, response );   
  HttpSession httpSession = null;   
  try  
  {   
   httpSession = request.getSession( false );   
  }   
  catch ( IllegalStateException ignored )   
  {   
  }   
  if ( httpSession != null )   
  {   
   if ( httpSession.getAttribute( Constants.USER ) == null )   
   {   
    if ( !requiresAuth )   
    {   
     SecurityContext sc = SecurityContextHolder.getContext();   
     Authentication auth = sc.getAuthentication();   
     if ( auth != null  
       && auth.getPrincipal() instanceof UserDetails )   
     {   
      UserDetails ud = (UserDetails) auth.getPrincipal();//上面声明的sql无非就是要包装成这个对象   
      User user = userManager.getUser( ud.getUsername() );从业务逻辑里找到用户,放到session里   
      httpSession.setAttribute( Constants.USER, user );   
     }   
    }   
   }   
  }   
  return requiresAuth;   
 }   
}   


在看看我的login.htm在登陆成功时是怎么工作的吧?


public class UserAction extends BaseAction   
{   
 private UserManager mgr;   
  
 public void setUserManager( UserManager mgr )   
 {   
  this.mgr = mgr;   
 }   
  
 public ActionForward login( ActionMapping mapping , ActionForm form ,   
   HttpServletRequest request , HttpServletResponse response )   
   throws Exception   
 {   
  User user = (User) getSessionObject( request, Constants.USER );   
  ActionMessages msg = new ActionMessages();   
  if ( user != null )   
  {   
   return new ActionForward(  user.getType() + ".htm", true );成功就去type.htm   
  }   
  else  
  {   
   String error = getParameter( request, Constants.ERROR );   
   if ( error != null )对于不同的错误,都加以提示   
   {   
    if ( error.equalsIgnoreCase( "wrong" ) )   
     msg.add( "msg", new ActionMessage( "fail.login.wrong" ) );   
    else if ( error.equalsIgnoreCase( "too" ) )   
     msg.add( "msg", new ActionMessage( "fail.login.too" ) );   
    else if ( error.equalsIgnoreCase( "fail" ) )   
     msg.add( "msg", new ActionMessage( "fail.login.fail" ) );   
    else  
     msg.add( "msg", new ActionMessage( "fail.login.please" ) );   
   }   
   else  
    msg.add( "msg", new ActionMessage( "fail.login.please" ) );   
  }   
  saveErrors( request, msg );   
  return mapping.findForward( "fail" );   
 }   
  
}   


当然,Acegi需要介绍的东西太多了,我只把我这次认为有必要解释的东西写在了上面让大家来参考,作为能google到的东西,比如对于认证的方式还有很多,我就没有详细的介绍,在学习Acegi过程中,把它自带的例子弄清楚很关键,希望大家一起学习一起共勉!
分享到:
评论
1 楼 eafy81 2008-03-05  
<bean id="daoAuthenticationProvider" class="org.<SPAN class=hilite1>acegi</SPAN>security.providers.dao.DaoAuthenticationProvider">    
  <property name="userDetailsService">    
   <bean id="jdbcDaoImpl"    
            class="org.<SPAN class=hilite1>acegi</SPAN>security.userdetails.jdbc.JdbcDaoImpl">    
          <property name="dataSource" ref="dataSource"/>    
          <property name="usersByUsernameQuery">    
              <value>    
                  select user,password,about from user where user = ?        查找用户的查询语句,只需要把你数据库中的用户和密码以及enable相对应上就行     
              </value>    
          </property>    
          <property name="authoritiesByUsernameQuery">    
              <value>    
                  select user,type from user where user = ?           这里就是把用户和权限对应上,在appfuse中用的两个表,我都放一个表里了,所以就用这一个就行问题的关键是要让它能找到两个字段,构成一个对象     
              </value>    
          </property>    
      </bean>    
  </property>    

这样不是通过jdbc认证吗,能通过自已的service认证,这样service里可以用hibernate

相关推荐

    struts + spring + hibernate + velocity + ajax + jotm + acegi

    struts + spring + hibernate + velocity + ajax + jotm + acegi

    Spring+Hibernate+Acegi 的初次体验

    在本文中,我们将探讨如何整合Spring、Hibernate和Acegi框架,构建一个安全的J2EE应用程序。Acegi是Spring Security的前身,它提供了一套强大的访问控制和认证机制,适用于复杂的权限管理需求。 首先,我们要了解...

    Struts2+Spring+Hibernate3+Acegi.rar

    Struts2、Spring和Hibernate3是Java开发中的三大框架,它们各自负责Web应用的不同层面,而Acegi(现已被Spring Security替代)则是一个强大的安全框架。这个名为"Struts2+Spring+Hibernate3+Acegi.rar"的压缩包提供...

    Acegi + Spring + Hibernate + Struts2搭建

    本篇文章将探讨如何使用Acegi、Spring、Hibernate和Struts2这四大组件共同构建一个基于角色的权限控制系统(Role-Based Access Control, RBAC),以确保系统的安全性。 首先,我们需要理解认证和授权这两个基本的...

    Acegi+Hibernate+Spring+JSF列子

    在这个"Acegi+Hibernate+Spring+JSF例子"中,我们可以看到这四个框架如何协同工作,为开发者提供一套完整的解决方案。 首先,让我们逐一了解这些框架的核心功能: 1. **Acegi**(现已被Spring Security替代):...

    Acegi + Spring + Hibernate + Struts 2搭建基于角色的权限控制系统

    Acegi(现已被Spring Security替代)是一个强大的安全框架,可以与Spring、Hibernate和Struts 2等其他技术结合,实现基于角色的权限控制(Role-Based Access Control, RBAC)。这篇文档将探讨如何使用Acegi,以及SSH...

    easyui+spring+hibernate示例,带权限管理

    在权限管理方面,Spring Security(原名Acegi)模块是关键,它提供了一套完整的认证和授权机制。通过Spring Security,我们可以定义用户角色、权限规则,实现登录验证、访问控制等功能,确保只有合法用户能访问特定...

    STRUTS+SPRING+HIBERNATE内部培训教程

    此外,通过集成Acegi(现为Spring Security)安全框架,可以提供用户认证和授权,增强系统安全性。 SSH的集成使用,让开发者能够专注于业务逻辑,而不必过于关心底层技术细节。它不仅提高了开发效率,还提升了系统...

    FLEX:集成Spring+Hibernate

    安全方面,Spring Security或Acegi(Spring的早期安全模块)可以用来保护应用的资源,提供认证和授权功能。通过配置Spring Security,我们可以限制对特定服务的访问,确保只有经过身份验证的用户才能执行敏感操作。 ...

    struts+spring+hibernate的登陆系统

    此外,Spring Security(原名Acegi)可以用于实现用户的认证与授权,提供安全的登录功能,防止未授权的访问。 **Hibernate** Hibernate是一个对象关系映射(ORM)工具,它简化了Java应用与数据库之间的交互。在登录...

    权限管理系统struts2+spring+hibernate+mysql

    同时,Spring Security(原Acegi)是Spring生态中的安全模块,提供了全面的身份验证和授权服务,可以方便地集成到系统中,实现用户登录、角色管理、访问控制列表(ACL)等功能。 3. **Hibernate**:Hibernate是一个...

    struts2+spring+hibernate实验设备管理系统

    Spring Security(前身是Acegi Security)可以集成到Spring框架中,提供细粒度的访问控制。在本系统中,可能已经通过Spring Security实现了用户登录验证、角色分配和权限控制,确保只有授权用户才能访问特定的资源。...

    struts2+spring+hibernate权限系统

    同时,Spring Security(原名Acegi)是其安全模块,提供了用户认证和授权的功能,可以实现角色级别的权限控制,支持基于URL、方法或特定业务规则的访问控制。 3. **Hibernate框架**:Hibernate是Java领域的一个ORM...

    DWR2+EXTJS2.2+Hibernate3.3+Spring2+Acegi 综合管理系统

    DWR2+EXTJS2.2+Hibernate3.3+Spring2+Acegi 做的综合管理系统,数据库采用MYSQL,分层清晰,业务相对复杂,是学习框架不可多得的项目。这个是分卷,同时下载2.3.4部分

    Struts2.0+spring2.0+hibernate3.1 ACEGI应用示例

    Struts2.0+spring2.0+hibernate3.1 ACEGI应用示例

    Struts2+Spring2+Hibernate3实现登录

    此外,Spring的安全模块(Spring Security,前身是Acegi)可以用于更复杂的权限控制,但这里可能只是实现了基本的身份验证。 Hibernate3是一个对象关系映射(ORM)框架,它简化了Java应用与数据库之间的交互。在...

    DWR2+EXTJS2.2+Hibernate3.3+Spring2+Acegi 综合管理系统(第二部分)

    DWR2+EXTJS2.2+Hibernate3.3+Spring2+Acegi 做的综合管理系统,数据库采用MYSQL,分层清晰,业务相对复杂,是学习框架不可多得的项目。

Global site tag (gtag.js) - Google Analytics