附录 C. Spring Security-3.0.0.M1

todolist

spring-el

pre-post-annotations

success-handler

rest

debug

firewall

Spring Security-3.0.0.M1于2009-05-27发布。

Spring Security-3.0.0.M2与2009-08-22发布。

Spring Security-3.0.0.CR1与2009-10-11发布。

Spring Security从2.0.x一跃而至3.0.0.M1,这第一个里程碑里主要有三点值得我们注意:

C.1. Hello World

使用Spring Security-3.0.0.M1制作了一个Helloworld,不需要修改项目中的任何配置就可以正常运行,这点上很佩服Spring系列的兼容性。

实例在x01下。

C.2. Spring-EL

下面详细介绍一下Spring Security 3中最新支持的Spring-EL表达式。

如果在配置中启用Spring-EL表达式功能,只要为http标签配置一个参数即可。

<http auto-config="true" use-expressions="true">
    <intercept-url pattern="/admin.jsp" access="hasRole('ROLE_ADMIN') and hasIpAddress('192.168.1.0/24')" />
    <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
</http>
        

唯一的问题就是,如果启用了Spring-EL就不能再使用原来的方式了,所有的权限都要使用Spring-EL表达,否则会在访问被保护资源时抛出无法解析Spring-EL的异常。

既然说到Spring-EL就不能不提Spring Security-3.0.0.M1中新支持的Pre-Post注解,它是用来替代MethodInvocationInterceptor和AfterInvocationInterceptor一套新注解。

启用Pre-Post注解

<global-method-security secured-annotations="enabled"
                        pre-post-annotations="enabled">
    <expression-handler ref="expressionHandler"/>
</global-method-security>
        

调用前权限控制。

拥有ROLE_USER或ROLE_ADMIN才能调用save()方法。

@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
public void save(String messageContent, String owner) {
    // ...
}
        

参数message的owner属性与principal的name属性相同才允许调用modifyMessage()方法。

@PreAuthorize("#message.owner == principal.name")
public void modifyMessage(Message message, String messageContent) {
    // ...
}
        

拥有对message的acl管理权限才可以调用deleteMessage()方法。

@PreAuthorize("hasPermission(#message, 'admin')")
public void deleteMessage(Message message) {
    // ...
}
        

调用后权限控制。

判断是否有权返回对象,拥有对返回对象的read或admin的acl权限才可以查看Message对象。

@PostAuthorize("hasPermission(returnObject, 'read') or hasPermission(returnObject, 'administration')")
public Message get(Long id) {
    // ...
}
        

将返回集合对象中无权限的信息过滤掉。将List中不符合不具有read或admin的acl权限的对象从list过滤掉。

@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'administration')")
public List getAll() {
    // ...
}
        

Spring-EL十分强大,这样应用也比使用MethodInvocationInterceptor和AfterInvocationInterceptor更清晰便捷。

实例在x02下。

C.3. RoleHierarchy

这个问题后来发现是因为wesee同志搞错了,想用Role继承实现User继承的功能。实际上Sid中实现Rolehierarchy应该是没有任何意义的,反倒是user继承应该还有点儿意思。

使用RoleHierarchy的实例在x03下。

C.4. Success Handler

为了实现更好的扩展性,3.x中开始专门提供了AuthenticationSuccessHandler和AuthenticationFailureHandler,用这两者控制用户认证成功和失败的情况。我们可以自己实现successHandler,在用户登录成功之后向session中任意放任何东西了。

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
        Authentication authentication) throws ServletException, IOException {

    HttpSession session = request.getSession();
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    if (userDetails.getUsername().equals("user")) {
        session.setAttribute("email", "user@163.com");
    } else if (userDetails.getUsername().equals("admin")) {
        session.setAttribute("email", "admin@163.com");
    }

    super.onAuthenticationSuccess(request, response, authentication);
}
        

实例在x04下。

C.5. REST下的权限控制

RESTful是蛮流行的一种web资源访问方式,它最大限度利用了http协议的功能表达资源请求的内涵。

  • GET /app/messages:表示获得所有Message的信息。

  • GET /app/messages/1:表示获得id=1的Message的信息。

  • POST /app/messages:表示创建一条Message信息。

  • PUT /app/messages/1:表示更新id=1的Message的信息。

  • DELETE /app/messages/1:表示删除id=1的Message的信息。

从上面的例子中可以看出来,我们只用了/app/messages和/app/messages/*两类URL,就表示了Message的CRUD所有操作。这时如果我们希望对这些操作进行权限控制,就不能仅仅根据URL地址来判断了,而是需要限制http请求的method参数。

下面我们对这些操作做出限制,只有ROLE_ADMIN才能添加,更新,删除Message,ROLE_USER只能查看Message的信息。

<http auto-config='true'>
    <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
    <intercept-url pattern="/app/messages" access="ROLE_ADMIN" method="POST" />
    <intercept-url pattern="/app/messages/*" access="ROLE_ADMIN" method="PUT" />
    <intercept-url pattern="/app/messages/*" access="ROLE_ADMIN" method="DELETE" />
    <intercept-url pattern="/**" access="ROLE_USER" />
</http>
        

在intercept-url中使用method指定对应的http请求method参数就可以限制REST下定义的不同功能请求了,可以每次只能定义一个method,如果支持method="PUT,DELETE"这种形式的配置就更方便了。

实例参考x05。

C.6. 管理会话同步

x06里演示了session-management和concurrency-control的配置,以及如何给自定义filter提供session控制策略。

C.7. debug调试模式

在xml中添加一个debug标签,就会启用调试模式。

<debug/>
		

系统启动之后,会提示debug模式已经启动。

2012-12-9 14:50:02 org.springframework.security.config.debug.SecurityDebugBeanFactoryPostProcessor postProcessBeanDefinitionRegistry
警告:

********************************************************************
**********        Security debugging is enabled.       *************
**********    This may include sensitive information.  *************
**********      Do not use in a production system!     *************
********************************************************************
		

启动了调试模式之后,每次请求都会打印日志,详细记录处理每次请求的信息。

2012-12-9 14:57:58 org.springframework.security.config.debug.Logger log
信息:

************************************************************

Request received for '/':

GET /x08/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:17.0) Gecko/20100101 Firefox/17.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.9,en;q=0.8,zh-CN-ORACLE9I;q=0.8,fr-FR;q=0.7,fr;q=0.7,en-gb;q=0.6,utf-8;q=0.6,u
tf;q=0.5,ja-JP;q=0.5,ja;q=0.4,de-DE;q=0.4,de;q=0.3,zh-Hans;q=0.3,en-AU;q=0.2,zh-TW;q=0.2,pt-BR;q=0.1,pt;q=0.1
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost:8080/x08/spring_security_login
Cookie: JSESSIONID=q7iqeut5ws89a4c74e2b8jfh



servletPath:/
pathInfo:null

Security filter chain: [
  SecurityContextPersistenceFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************