转:spring security3 初探  http://yzxqml.iteye.com/blog/1756106

现在很多企业和开发团队都使用了SSH2(Struts 2 +Spring 2.5 +Hibernate)框架来进行开发,  我们或许已经习惯了强大的Spring Framework 全局配置管理,不可否认,Sping是一个很优秀的开源框架,但是由于Spring3.0版本后强大的的注解式bean的诞生,Spring MVC框架这匹黑马正悄然杀起,但今天Spring MVC不是主角,今天我和大家分享一个同样隶属于SpringSource 的安全框架——Spring Security, 下面的基于Spring MVC给大家分享一下Spring Security  的使用。虽然对它的接触时间不长,参考了一些网上朋友的做法,但也按照我的理解把这个框架介绍介绍,不是很专业,还请大家不要介意 。

我们知道,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个资源来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

首先,我们看web.xml

Java代码  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  4. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  5. <!-- 编码统一最好放最上面,最先加载,防止乱码-->
  6. <filter>
  7. <filter-name>Set Character Encoding</filter-name>
  8. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  9. <init-param>
  10. <param-name>encoding</param-name>
  11. <param-value>UTF-8</param-value>
  12. </init-param>
  13. <init-param>
  14. <param-name>forceEncoding</param-name>
  15. <param-value>true</param-value><!-- 强制进行转码 -->
  16. </init-param>
  17. </filter>
  18. <filter-mapping>
  19. <filter-name>Set Character Encoding</filter-name>
  20. <url-pattern>/*</url-pattern>
  21. </filter-mapping>
  22. <!-- 然后接着是SpringSecurity必须的filter 优先配置,让SpringSecurity先加载,防止SpringSecurity拦截失效-->
  23. <filter>
  24. <filter-name>springSecurityFilterChain</filter-name>
  25. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  26. </filter>
  27. <filter-mapping>
  28. <filter-name>springSecurityFilterChain</filter-name>
  29. <url-pattern>/*</url-pattern>
  30. </filter-mapping>
  31. <welcome-file-list>
  32. <welcome-file>index.jsp</welcome-file>
  33. </welcome-file-list>
  34. <!--
  35. spring需要加载的配置文件
  36. -->
  37. <context-param>
  38. <param-name>contextConfigLocation</param-name>
  39. <param-value>
  40. WEB-INF/classes/applicationContext.xml,
  41. WEB-INF/spring3-servlet.xml,
  42. WEB-INF/spring-security.xml
  43. </param-value>
  44. </context-param>
  45. <listener>
  46. <listener-class>
  47. <!--     所以,要在web.xml下面配置好监听,让服务器启动时就初始化改类,可以得到request   -->
  48. org.springframework.web.context.request.RequestContextListener
  49. </listener-class>
  50. </listener>
  51. <listener>
  52. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  53. </listener>
  54. <!--
  55. 默认所对应的配置文件是WEB-INF下的{servlet-name}-servlet.xml,这里便是:spring3-servlet.xml
  56. -->
  57. <servlet>
  58. <servlet-name>spring3</servlet-name>
  59. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  60. <load-on-startup>1</load-on-startup>
  61. </servlet>
  62. <servlet-mapping>
  63. <servlet-name>spring3</servlet-name>
  64. <!--
  65. 这里可以用 / 但不能用 /*
  66. ,拦截了所有请求会导致静态资源无法访问,所以要在spring3-servlet.xml中配置mvc:resources
  67. -->
  68. <url-pattern>/</url-pattern>
  69. </servlet-mapping>
  70. </web-app>

注释已经写了挺多,还是稍微解释一下要注意的地方,一个是UTF-8编码转换,这个最好加在最前面,让它先生效,我在调试的时候就出过这种情况,web.xml里的其他配置都正常生效了,但是编码死活不行,一中文就乱码,郁闷了老半天,然后突发奇想,是不是web.xml里先声明的配置先生效,后声明的后生效?接着实践,果然不出我所料,把编码转换加在前面,一切正常。。。。我那个晕。。。

关于Spirng MVC的就不说了,那些数据访问、业务和控制层那些的东东就自个研究去了吧。。这不是今天的重点。

接着是spring-security.xml

Java代码  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:security="http://www.springframework.org/schema/security"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  6. http://www.springframework.org/schema/security
  7. http://www.springframework.org/schema/security/spring-security-3.0.xsd">
  8. <!--  Spring-Security 的配置 -->
  9. <!--
  10. 注意use-expressions=true.表示开启表达式,否则表达式将不可用. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html
  11. -->
  12. <security:http auto-config="true" use-expressions="false" access-denied-page="/user/login_failure.html">
  13. <!--允许所有人访问-->
  14. <!--     <security:intercept-url pattern="/**" access="permitAll" />-->
  15. <!--允许ROLE_ADMIN权限访问-->
  16. <security:intercept-url pattern="/user/findAll.html" access="ROLE_ADMIN" />
  17. <!--允许ROLE_ADMIN权限访问-->
  18. <security:intercept-url pattern="/user/**" access="ROLE_ADMIN" />
  19. <!--允许ROLE_USER权限访问-->
  20. <security:intercept-url pattern="/success.jsp" access="ROLE_USER,ROLE_ADMIN" />
  21. <!--允许IS_AUTHENTICATED_ANONYMOUSLY匿名访问-->
  22. <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
  23. <!-- filters="none"  不过滤这些资源-->
  24. <security:intercept-url pattern="/js/**" filters="none" />
  25. <security:intercept-url pattern="/index.jsp" filters="none" />
  26. <!-- login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面. default-target-url:成功登陆后跳转页面 -->
  27. <security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html"
  28. default-target-url="/success.jsp" />
  29. <!--
  30. invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。
  31. -->
  32. <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/j_spring_security_logout" />
  33. <!--
  34. max-sessions:允许用户帐号登录的次数。范例限制用户只能登录一次。exception-if-maximum-exceeded:
  35. 默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当exception-if-maximum-exceeded="true"时系统会拒绝第二次登录。
  36. -->
  37. <security:session-management>
  38. <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" />
  39. </security:session-management>
  40. </security:http>
  41. <!-- 指定一个自定义的authentication-manager :customUserDetailsService -->
  42. <security:authentication-manager>
  43. <security:authentication-provider user-service-ref="customUserDetailsService">
  44. <security:password-encoder ref="passwordEncoder" />
  45. </security:authentication-provider>
  46. </security:authentication-manager>
  47. <!-- 对密码进行MD5编码 -->
  48. <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
  49. <!--
  50. 通过 customUserDetailsService,Spring会控制用户的访问级别.
  51. 也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联.
  52. -->
  53. <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" />
  54. <!-- 自定义登陆错误提示,可以取出mymessages.properties的国际化消息-->
  55. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  56. <property name="basename" value="classpath:org/yzsoft/springmvcdemo/mymessages" />
  57. </bean>
  58. <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" />
  59. </beans>

这个多解释一下,首先

Java代码  
  1. <security:intercept-url pattern="/findAll.html" access="hasRole('ROLE_ADMIN')" />
  2. <security:intercept-url pattern="/user/**" access="hasRole('ROLE_ADMIN')" />
  3. <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />

这个是权限控制,声明了拥有什么权限可以访问哪些资源,这个配置的是有ROLE_ADMIN权限的才可以访问/findAll.html,至于这个ROLE_ADMIN从哪来,呆会再解释。或者像第二句一样配置也可以:拥有ROLE_ADMIN权限的才可以访问/user/下的所有资源,否则都会抛出AccessDeniedException 。

IS_AUTHENTICATED_ANONYMOUSLY就是匿名访问的意思,这个相信都懂的。。。

然后是登陆和安全退出

Java代码  
  1. <security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html"default-target-url="/user/findAll.html" />
  2. <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/user/login_failure.html" />

解释下上面一句,相信看也能看出来了的,login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面(包括那些个啥用户名密码错误吖。。啥啥啥的。). default-target-url:成功登陆后跳转页面 (这里我直接跳到的控制器去查数据列表)。

接着:invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。

接下来是一个比较不错的功能:是否允许同一用户多处登陆

Java代码  
  1. <security:session-management>
  2. <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" />
  3. </security:session-management>

exception-if-maximum-exceeded:
   默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当error-if-maximum-exceeded="true"时系统会拒绝第二次登录。

max-sessions:允许用户帐号登录的次数,这里我们允许一次登陆。这里我们做个实验吧,看看是不是真的生效了,请看图

这里我们看到,当同一个账号多处登陆时,就会报出Maximum sessions of 1 for this principal exceeded 的错误,当然,正式使用我们换成mymessages.properties里的我们自定义的国际化消息,这样人性化一点。

好,我们往下看,接着就是应用我们实际项目里的自定义用户权限了

Java代码  
  1. <security:authentication-manager>
  2. <security:authentication-provider user-service-ref="customUserDetailsService">
  3. <security:password-encoder ref="passwordEncoder" />
  4. </security:authentication-provider>
  5. </security:authentication-manager>
  6. <!-- 对密码进行MD5编码 -->
  7. <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
  8. <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" />

首先是<security:authentication-manager>是指定我们自定义的身份验证策略,这里我们用customUserDetailsService这个bean,就是指向我们CustomUserDetailsService.java这个类。然后<security:password-encoder>指定我们密码使用MD5进行编码,调用Spring Security自带的MD5加密类。当然,还有加盐MD5或我们自己写的加密算法等安全性更加高的密码策略。这个按项目实际使用配置吧。

然后看到我们的CustomUserDetailsService.java

Java代码  
  1. package org.yzsoft.springmvcdemo.util;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.List;
  5. import org.apache.log4j.Logger;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.dao.DataAccessException;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.security.core.authority.GrantedAuthorityImpl;
  10. import org.springframework.security.core.userdetails.User;
  11. import org.springframework.security.core.userdetails.UserDetails;
  12. import org.springframework.security.core.userdetails.UserDetailsService;
  13. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  14. import org.yzsoft.springmvcdemo.serviceimpl.UsersServiceImpl;
  15. import org.yzsoft.springmvcdemo.vo.TUsers;
  16. /**
  17. * 一个自定义的类用来和数据库进行操作. 即以后我们要通过数据库保存权限.则需要我们继承UserDetailsService
  18. *
  19. * @author
  20. *
  21. */
  22. public class CustomUserDetailsService implements UserDetailsService {
  23. protected static Logger logger = Logger.getLogger("service");//log4j,不用解释了吧。。
  24. @Autowired
  25. private UsersServiceImpl usersService;
  26. public UsersServiceImpl getUsersService() {
  27. return usersService;
  28. }
  29. public void setUsersService(UsersServiceImpl usersService) {
  30. this.usersService = usersService;
  31. }
  32. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
  33. UserDetails user = null;
  34. try {
  35. // 搜索数据库以匹配用户登录名.
  36. // 我们可以通过dao使用Hibernate来访问数据库
  37. System.out.println(username + "   用户页面输入的用户名");
  38. TUsers tusers = this.usersService.findByUsername(username);
  39. System.out.println(tusers.getUsername() + "   数据库取出的用户名");
  40. // Populate the Spring User object with details from the dbUser
  41. // Here we just pass the username, password, and access level
  42. // getAuthorities() will translate the access level to the correct
  43. // role type
  44. // 用户名、密码、是否启用、是否被锁定、是否过期、权限
  45. user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole())));
  46. } catch (Exception e) {
  47. logger.error("用户信息错误!");
  48. throw new UsernameNotFoundException("异常处理:检索用户信息未通过!");
  49. }
  50. return user;
  51. }
  52. /**
  53. * 获得访问角色权限列表
  54. *
  55. * @param access
  56. * @return
  57. */
  58. public Collection<GrantedAuthority> getAuthorities(Integer role) {
  59. System.out.println("取得的权限是  :" + role);
  60. List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
  61. // 所有的用户默认拥有ROLE_USER权限
  62. if (role == 0) {
  63. System.out.println("普通用户");
  64. logger.debug("取得普通用户权限-->");
  65. authList.add(new GrantedAuthorityImpl("ROLE_USERS"));
  66. }
  67. // 如果参数role为1.则拥有ROLE_ADMIN权限
  68. if (role == 1) {
  69. logger.debug("取得ADMIN用户权限-->");
  70. authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
  71. }
  72. System.out.println(authList.size()+"  权限列表长度");
  73. return authList;
  74. }
  75. }

这里就是把我们从数据库里面取得的用户权限和Spring Security的配置进行桥接,还记得上面配置文件里的ROLE_ADMIN吧,就是从这里来的,很奇怪的是,这个必须设置成ROLE_ 开头才有效。。郁闷。。。

这里我刚开始有一个疑惑,我们看这2句

Java代码  
  1. TUsers tusers = this.usersService.findByUsername(username);
  2. user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole())));

这里根本不需要用户输入的密码,只要了用户名,然后直接根据用户名去取权限,就直接设置进Spring Security的User对象里面去,我不禁一身冷汗,这不相当于说有了用户名就直接去查数据库么,而且是不用密码的。。。。

但经过查看官方文档和网上的解释,这才放心,原来是这样的,Spring Security的确是直接根据用户名去查,但是查得出来的Spring Security  User对象之后,它会根据这个对象的属性值去数据库查询与这个对象匹配的数据,我们这里设置的是(用户名,密码,是否启用、是否被锁定、是否过期、权限。。。),那么如果数据库存在这个对象,就返回真,否则返回假,这样也就不用担心了,完全可靠。就是我们在前台要做好限制,不能给用户不输密码就访问, 不然挤爆你数据库连接。。。。。

最后登陆页面index.jsp

Java代码  
  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
  2. <%
  3. String path = request.getContextPath();
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  5. %>
  6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  7. <html>
  8. <head>
  9. <base href="<%=basePath%>">
  10. <title>My JSP 'index.jsp' starting page</title>
  11. <meta http-equiv="pragma" content="no-cache">
  12. <meta http-equiv="cache-control" content="no-cache">
  13. <meta http-equiv="expires" content="0">
  14. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  15. <meta http-equiv="description" content="This is my page">
  16. </head>
  17. <body>
  18. 用户登陆 <br>
  19. ${SPRING_SECURITY_LAST_EXCEPTION.message}
  20. <form action="j_spring_security_check" method="post">
  21. USERNAME:<input type="text" name="j_username" value="${sessionScope['SPRING_SECURITY_LAST_USERNAME']}" /><br/>
  22. PASSWORD:<input type="password" name="j_password" value="" /><br/>
  23. <input type="checkbox" name="_spring_security_remember_me" />两周之内不必登陆(这个功能没有做的)<br/>
  24. <input type="submit">
  25. </form>
  26. </body>
  27. </html>

这里我还是使用Spring Security默认的j_username和j_password,表单目标也用默认的j_spring_security_check,会默认跳到Spring Security进行拦截。其他的应该不用解释了吧。。。。

最后 ,我们整理一下Spring Security的整个控制过程:

——>用户登陆

——> <security:authentication-manager> 拦截

——>交给customUserDetailsService处理,并且声明密码采用MD5策略

——>根据输入的用户名去数据库查这条记录,验证身份

——>取出该条记录的用户权限(锁定、禁用、过期和实际权限等)

——>根据取得的权限列表去security:intercept-url匹配、授权,然后判断是否放行。

这就完成了一整个的权限控制流程。

接下来我们来测试一下看是否真的生效了:

1、测试匿名访问页面,直接地址栏访问:

2、普通用户登陆

然后访问后台管理页面,跳回了登陆页

3、管理员用户登陆

退出后成功跳转回登陆页,点击后退再执行其他操作,这时候session已经注销了的,不能执行,又跳回了登陆页。是我们想要的效果,OK,成功了。

好了,Spring Security的简单使用就讲到这里,其实这只是Spring Security的一小部分,而且这里我还没有用权限表对用户权限进行专门的管理,很多东西还是用Spring Security 默认的,还有Spring Security CAS (单点登陆)以及更加高级的权限控制和更完善的Spring Security 配置,以后我们再慢慢去研究吧。发现Spring Security 这个技术不仅简化了我们的用户权限管理,要知道我们做管理系统的时候这是个大问题,也差不多颠覆了我一贯以来用户权限管理的观念,但是掌握了这种思维之后,又发现,其实,程序并不是只有一种实现方式,它激发了我写程序时要去寻找多种解决方案的想法。学习的路上,就是要不断推翻自己固有的思维,去见识多种新事物,才能有进步。

转载于:https://www.cnblogs.com/cczhoufeng/archive/2013/03/06/2945440.html

spring security3(转)相关推荐

  1. 使用Spring Security3的四种方法概述

    使用Spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...

  2. spring security3 统计在线用户

    首先,我们需要使得ConcurrentSessionFilter生效并在spring-security.xml配置. [html] view plaincopy <http auto-confi ...

  3. 关于对《Spring Security3》翻译 (第一章 - 第三章)

    原文:http://lengyun3566.iteye.com/category/153689?page=2 翻译说明 最近阅读了<Spring Security3>一书,颇有收获(封面见 ...

  4. 春天的故事-Spring Security3十五日研究

    sparta-紫杉   2011-4-2 22:00 前言 南朝<述异记>中记载,晋王质上山砍柴,见二童子下棋,未看完,斧柄已烂,下山回村,闻同代人都去世了,自已还未变老.     因此发 ...

  5. Spring Security3十五日研究

    前言 南朝<述异记>中记载,晋王质上山砍柴,见二童子下棋,未看完,斧柄已烂,下山回村,闻同代人都去世了,自已还未变老.     因此发出"山中方一日,世上几千年" 的慨 ...

  6. spring security3.x学习(12)_remember me

    2019独角兽企业重金招聘Python工程师标准>>> 本文为转载学习 原文链接:http://blog.csdn.net/dsundsun/article/details/1182 ...

  7. Spring Security3源码分析-http标签解析(转)

    为什么80%的码农都做不了架构师?>>>    在FilterChainProxy初始化的过程中,大概描述了标签解析的一些步骤,但不够详细   <http auto-confi ...

  8. 《Spring Security3》第四章第一部分翻译下(自定义的UserDetailsServic

    2019独角兽企业重金招聘Python工程师标准>>> 创建一个自定义的JDBC UserDetailsService 在com.packtpub.springsecurity.se ...

  9. spring security3.x学习(15)_扩展JdbcDaoImpl。

    2019独角兽企业重金招聘Python工程师标准>>> 本文为转载学习 原文链接:http://blog.csdn.net/dsundsun/article/details/1184 ...

最新文章

  1. 我也说说Emacs吧(6) - Lisp速成
  2. linux变量接收命令返回值,Linux Shell教程(一)
  3. DotNet Framework源代码调试问题
  4. python各种类型日期转换大全
  5. IDEA运行第一个Spring Boot应用程序
  6. MATLAB生成正态样本以及正态矩阵、从文件读入矩阵
  7. 记录一下ui设计中的网站配色
  8. JAVA Web学习篇--Servlet
  9. vue-router-2-动态路由配置
  10. 再看lambda/sorted/filter/map
  11. android 时间戳 转日期格式,在Android中转换为简单日期格式或Unix时间戳日期?
  12. Python基础(循环控制语句break/continue)
  13. 广度优先搜索算法(Breath-first Search)是如何搜索一张图的?
  14. Java 全栈工程师进阶路线图
  15. 几个比较好用的爬虫库
  16. 自然常数e的由来以及计算机为什么是二进制
  17. 银行计算机岗社会招聘考什么题目,2020各大银行科技岗招聘考试考试内容是什么?...
  18. win10 添加打印机完整图文教程演示
  19. Matlab画三维立体网状图形(类似魔方)
  20. cocos2d-x小游戏——飞机大战

热门文章

  1. 记录下kaggle比赛经验
  2. 自动驾驶路径规划论文解析(4)
  3. c语言调用机器码,演示几种用C语言来执行shellcode(其实也就是机器码)的方式
  4. 饿了么java_eleme-openapi-java-sdk
  5. react 父子传值_React父子组件间的传值
  6. wireshark使用_使用 Wireshark 抓取数据包
  7. lua 给userdata设置元表_UE4热更新:基于UnLua的Lua编程指南
  8. JBox2d入门学习二 -----我的小鸟
  9. 集合覆盖模型例题_在打CodeForces的过程中发现的一个小模型
  10. Axure RP chrome插件显示已损坏或者无法安装的解决方法