|
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
时间:2007-02-07 关键字: acegi,安全,web acegi
最近两星期在学习acegi,过程中感谢JavaEye,SpringSide和在网上提供acegi学习心得的网友们。
为了加深自己的认识,准备写下一些DEMO,希望可以给准备学习acegi的同学一些帮助。 作为安全服务离不开认证和授权这两个主要组成部分。而这篇文章就是针对acegi的认证服务。 《学习Acegi-认证(authentication)》 代码环境基于: JDK1.5 acegi1.0.3 spring2.0 IDE基于: Eclipse3.2+MyEclipse5.0.1 面向人员: 熟悉Eclipse+MyEclipse开发但刚开始了解acegi的人员。如果你是高手请指出文章不足之处。 1.建立一个MyEclipse的WebProject,把下列jar文件拷贝到项目的WEB-INF/lib目录: acegi-security-1.0.3.jar spring2.0.jar commons-codec-1.3.jar 费话说一句(占些字数):这是因为代码运行需要这些包的支持。 2.修改WEB-INF下的web.xml文件,内容如下: <?xml version="1.0" encoding="UTF-8"?> <web-app> <display-name>acegi Example of liuyxit</display-name> <!-- 定义应用的上下文参数,用于ContextLoaderListener --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/applicationContext.xml </param-value> </context-param> <!--acegi 的filter链代理--> <filter> <filter-name>Acegi Filter Chain Proxy</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>Acegi Filter Chain Proxy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 装载应用软件的Spring上下文 要由WebapplicationContextUtils.getWebApplicationnContext(servletContext)得到. --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app> 其中FilterChainProxy实现了filter接口,它主要是实例化FilterChainProxy,并把所有动作交由FilterChainProxy处理。这样简化了web.xml的配置,并且充分利用了Spring IOC管理Bean的优势。 3.在src目录右键新建一个resource folder,在下面再建立acegi和spring目录 在spring目录中创建applicationContext.xml文件,内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
default-autowire="byName" default-lazy-init="true">
<!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=authenticationProcessingFilter,exceptionTranslationFilter
</value>
</property>
</bean>
<!-- ======================== 认证filter ======================= -->
<!-- 表单认证处理filter -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/acegilogin.jsp?login_error=1"/>
<property name="defaultTargetUrl" value="/userinfo.jsp"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
</bean>
<!-- 认证管理器 -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers"><!-- 可有多个认证提供器,其中一个证通过就可以了 -->
<list>
<ref local="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
</bean>
<!-- 用户资料-->
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
liuyxit=123,ROLE_SUPERVISOR
user1=user1,ROLE_USER
user2=user2,disabled,ROLE_USER
</value>
</property>
</bean>
<!-- 异常处理filter -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp"/>
</bean>
</property>
</bean>
</beans>
[list=A]其中filterChainProxy就是由web.xml声明的filter(FilterToBeanProxy)的targetClass。它主要是装载filterInvocationDefinitionSource指定的filter类(例子中为authenticationProcessingFilter,exceptionTranslationFilter),并顺序调用它们的doFilter方法,进行安全服务处理。[/list] [list=B]而authenticationProcessingFilter是处理一个认证表单,登陆用的表单必须提交用户名和密码这两个参数给这个filter.由用户名和密码构造一个UsernamePasswordAuthenticationToken,将传给AuthenticationManager的authenticate方法进行认证处理。该filter默认处理filterProcessesUrl属性指定的URL,认证失败会转到authenticationFailureUrl,认证成功会转到defaultTargetUrl页面。[/list] [list=C]AuthenticationManager顾名思义认证管理器,它只有一个接口方法authenticate用于返回认证结果,他的实现类由多个AuthenticationProvider进行投票,决定认证是否通过。[/list] [list=D]daoAuthenticationProvider是检验用户录入的认证数据是否正确(说白了就是用户名和密码是否正确)[/list] [list=E]inMemoryDaoImpl是给daoAuthenticationProvider提供系统的用户资料。而资料的来源是从配置中装载到内存的。[/list] [list=F]当认证不通过时,AuthenticationManager的实现类AbstractAuthenticationManager会抛出AuthenticationException类型的异常。这时排在最后的exceptionTranslationFilter会捕获该异常,并转向authenticationEntryPoint。[/list] 4.在WebRoot下创建index.jsp(其实不要也没关系,主要是为了方便),直接转向用户资料显示页。内容如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<META HTTP-EQUIV="Refresh" CONTENT="0;URL=userinfo.jsp">
</head>
<body>
<p>Loading ...</p>
</body>
</html>
5.在WebRoot下创建userinfo.jsp,用于显示当前登陆的用户信息。内容如下
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page import="org.acegisecurity.context.SecurityContextHolder"%>
<%@ page import="org.acegisecurity.userdetails.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'pass.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
当前用户:
<%
Object obj = SecurityContextHolder.getContext().getAuthentication();
if (null != obj){
Object userDetail = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = "";
if (userDetail instanceof UserDetails) {
username = ((UserDetails) userDetail).getUsername();
} else {
username = userDetail.toString();
}
out.print(username);
out.print("<br><a href=\"j_acegi_logout\">注销</a>");
}else{
out.print("当前没有有效的用户");
out.print("<br><a href=\"acegilogin.jsp\">登陆</a>");
}
%>
</body>
</html>
6.在WebRoot下创建acegilogin.jsp
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<P>Valid users:
<P>
<P>username <b>liuyxit</b>, password <b>123</b> (supervisor)
<P>username <b>user1</b>, password <b>user1</b> (normal user)
<p>username <b>user2</b>, password <b>user2</b> (user disabled)
<p>
<%
String strError = request.getParameter("login_error");
if (null != strError){
%>
<font color="red">
你的登陆失败,请重试。<BR><BR>
原因: <%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>
</font>
<%
}//end if
%>
<form action="j_acegi_security_check" method="POST">
<table>
<tr><td>User:</td><td><input type='text' name='j_username' value='<%= session.getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY) %>'></td></tr>
<tr><td>Password:</td><td><input type='password' name='j_password'></td></tr>
<tr><td><input type="checkbox" name="_acegi_security_remember_me"></td><td>2周内自动登录</td></tr>
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>
</form>
</body>
</html>
7.OK,发布项目,访问http://localhost:8080/acegiexample 这时index.jsp会自动转向userinfo.jsp,由于还没有用户登录,所以没有资料显示。按登陆链接进入登录页,登录成功后会看到显示用户名的页面(当然可以有更多的用户资料,但这仅仅是example),不成功时会在登录页提示信息。我们可以用user1和user2登陆,可以分别测试登录成功和失败的流程。 8.可以看到登录页上有自动登陆功能,而userinfo.jsp页有注销功能。但还是不起作用,好,现在我们马上首手加入这两个功能。看acegi可以方便到什么程度。修改applicationContext.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
default-autowire="byName" default-lazy-init="true">
<!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy" >
<property name="filterInvocationDefinitionSource" >
<value >
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/** =authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,exceptionTranslationFilter
</value>
</property>
</bean>
<!-- ======================== 认证filter ======================= -->
<!-- 表单认证处理filter -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/acegilogin.jsp?login_error=1"/>
<property name="defaultTargetUrl" value="/userinfo.jsp"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
</bean>
<!-- 利用cookie自动登陆filter -->
<bean id="rememberMeProcessingFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<bean id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="key" value="javargb"/>
</bean>
<bean id="rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="javargb"/>
</bean>
<!-- 注销处理filter -->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/acegilogin.jsp"/> <!-- URL redirected to after logout -->
<constructor-arg>
<list>
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
<!-- 认证管理器 -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers"><!-- 可有多个认证提供器,其中一个证通过就可以了 -->
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
</bean>
<!--
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:acegi/users.properties"/>
</bean>
</property>
</bean>
-->
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
liuyxit=123,ROLE_SUPERVISOR
user1=user1,ROLE_USER
user2=user2,disabled,ROLE_USER
</value>
</property>
</bean>
<!-- 异常处理filter -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp"/>
</bean>
</property>
</bean>
</beans>
另要注意:Acegi默认的自动登陆设定参数名为_acegi_security_remember_me,注销链接为/j_acegi_logout。 马上重启Tomcat测试看看^_^。 9.通常用户资料会放在数据库中,而不会放在配置文件,接着下面我们来再行修改一下。 首先创建要用到的用户表和权限表,并插入初始化数据: CREATE TABLE USERS( USERNAME VARCHAR( 50 ) NOT NULL PRIMARY KEY, PASSWORD VARCHAR( 50 ) NOT NULL, ENABLED BIT NOT NULL) INSERT INTO USERS(username,password,enabled) values( ' liuyxit ' , ' 123 ' , ' 1 ' ) INSERT INTO USERS(username,password,enabled) values( ' user1 ' , ' user1 ' , ' 1 ' ) INSERT INTO USERS(username,password,enabled) values( ' user2 ' , ' user2 ' , ' 0 ' ) CREATE TABLE AUTHORITIES( USERNAME VARCHAR( 50 ) NOT NULL, AUTHORITY VARCHAR( 50 ) NOT NULL, CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME) ); INSERT INTO AUTHORITIES(USERNAME,AUTHORITY) values( ' liuyxit ' , ' ROLE_SUPERVISOR ' ) INSERT INTO AUTHORITIES(USERNAME,AUTHORITY) values( ' user1 ' , ' ROLE_USER ' ) INSERT INTO AUTHORITIES(USERNAME,AUTHORITY) values( ' user2 ' , ' ROLE_USER ' ) 这里我用的是acegi默认的数据结构,可以改只要你指定JdbcDaoImpl的authoritiesByUsernameQuery和usersByUsernameQuery属性就可以了。另AUTHORITIES表也要一同加入,原因acegi获得userDetail时,也会读取这个表的内容,否则会抛“nested exception is java.sql.SQLException: 对象名 'authorities' 无效”这个异常。 修改applicationContext.xml,变成如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
default-autowire="byName" default-lazy-init="true">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<!-- 请自行修改为对应你的数据库的驱动类 -->
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<!-- 请自行修改为对应你的数据库URL -->
<value>jdbc:jtds:sqlserver://localhost:1433/javauser</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value>javauser</value>
</property>
</bean>
<!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,exceptionTranslationFilter
</value>
</property>
</bean>
<!-- ======================== 认证filter ======================= -->
<!-- 表单认证处理filter -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/acegilogin.jsp?login_error=1"/>
<property name="defaultTargetUrl" value="/userinfo.jsp"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
</bean>
<!-- 利用cookie自动登陆filter -->
<bean id="rememberMeProcessingFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<bean id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="key" value="javargb"/>
</bean>
<bean id="rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="javargb"/>
</bean>
<!-- 注销处理filter -->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/acegilogin.jsp"/> <!-- URL redirected to after logout -->
<constructor-arg>
<list>
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
<!-- 认证管理器 -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers"><!-- 可有多个认证提供器,其中一个证通过就可以了 -->
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
</bean>
<bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<!-- 异常处理filter -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp"/>
</bean>
</property>
</bean>
</beans>
OK,大功告成。 后记:很少写技术文章,除了要坚持之外,文笔和思路都很重要。感觉自己的写作水平太差了,希望大家指出不合理的地方。有时间我会再写后篇《学习Acegi-授权(authorization)》,感谢大家把拙文看完,TKS! 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2007-02-07
回复坏贴中的firedragon:
firedragon 写道 请问SecurityContextHolder.getContext().getAuthentication();取到空值,可能是什么地方出了差错?
经检查必须给Spring的配置文件使用(本贴已修正)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
default-autowire="byName" default-lazy-init="true">
由于一开始使用Rich Editor编辑器,加入以上XML的命名空间属性,整个贴就乱掉了,发了N次不果,最后我去掉这个命名空间,使用了BBcode模式才可以。 请robbin或相关开发人员查查是不是BUG,另我在BLOG发了好次这篇文章,都是同步发到论坛的,现发现我删除的文章仍然在BLOG的发表文章列表中。论坛上也有另一篇,但我删除掉了,请版主们帮帮手。TKS! |
|
| 返回顶楼 | |
|
时间:2007-03-03
good,so good!!!
|
|
| 返回顶楼 | |
|
时间:2007-03-03
写得还是挺不错的,我是看明白了.另外,你提供的例子只是一小部分,javaEE安全在声明性、层间透明(穿透)上面有很多文章,楼主提供的仅仅是登陆认证和编程实现,希望能看到你后续关于高级应用的文章。
|
|
| 返回顶楼 | |
|
时间:2007-03-13
说实话,我从中受益了.
|
|
| 返回顶楼 | |
|
时间:2007-03-13
把demo打包上来效果会更好
把权限放在数据库存储会更实用 |
|
| 返回顶楼 | |
|
时间:2007-03-13
楼主关于内部细节的问题讲的还是有点粗略,不过作为入门性文章,已经很不错了,详细的东西毕竟还有open source的document
|
|
| 返回顶楼 | |
|
时间:2007-03-26
learning~
|
|
| 返回顶楼 | |
|
时间:2007-03-30
写的很好啊,支持一下,新作期待中......
|
|
| 返回顶楼 | |
|
时间:2007-03-30
温柔一刀 写道 把demo打包上来效果会更好
把权限放在数据库存储会更实用 是阿,打个包更方便看了,已经收藏了,好文章 |
|
| 返回顶楼 | |










