NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

Spring Web Security 实战 (二) - 自定义登录页面

| Comments

上一章节结尾,我们实现了Spring web security的最小实现,拦截所有url,采用表单登录,用户名和密码采用inMemory方式保存。

你可能在想,这个登录页面从哪里来的,我们并没有直接去实现一个页面。事实上,正是因为我们没有设置登录页面,Spring Security就自动生成了一个,该页面是的Spring Security处理登录的标准页面。

不过,我们肯定是不会想要使用Spring Security给我们提供的标准页面,除非项目刚启动,处于demo阶段,否则,肯定采用自定义页面,那么如何做到呢?

在form-login标签提供了一个属性login-page,允许指定用户自定义的登录页面,于是,实现方式如下:

首先定义一个登录页面JSP文件,里面的form表单和标准页面一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Spring MVC Security</title>
</head>
<body>
<form action="/spring-mvc-security/j_spring_security_check" method="POST">
    <table>
        <tbody>
        <tr>
            <td>User:</td>
            <td><input type="text" name="j_username" value=""></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="j_password"></td>
        </tr>
        <tr>
            <td colspan="2"><input name="submit" type="submit" value="Login"></td>
        </tr>
        </tbody>
    </table>
</form>
</body>
</html>

然后定义一个对应的RequestMapping:

1
2
3
4
5
6
7
@Controller
public class LoginController {
  @RequestMapping(value = "/login", method = GET)
  public String login() {
      return "login";
  }
}

最后一步,就是配置http标签的内容,有两点,第一,设置form-login的属性login-page为登录页面的url,第二,设置login页面的访问权限为IS_AUTHENTICATED_ANONYMOUSLY,必须允许匿名用户可以访问,否则怎么登录呢。代码如下:

1
2
3
4
5
6
<security:http>
    <security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    <security:intercept-url pattern="/**" access="ROLE_USER"/>
    <security:form-login login-page="/login"/>
    <security:logout/>
</security:http>

当然,你也可以这么写,3.1之后,允许多个http标签的定义。不过这么做,相当于完全绕过Spring Security的filterChain,不在Spring Security相关管理之下。

1
2
<http pattern="/css/**" security="none"/>
<http pattern="/login" security="none"/>

匿名登录

你肯定会问,上面那样写和IS_AUTHENTICATED_ANONYMOUSLY有什么区别?这里参考:http://www.mossle.com/docs/auth/html/ch107-anonymous.html

”匿名登录,即用户尚未登录系统,系统会为所有未登录的用户分配一个匿名用户,这个用户也拥有自己的权限,不过他是不能访问任何被保护资源的。

设置一个匿名用户的好处是,我们在进行权限判断时,可以保证SecurityContext中永远是存在着一个权限主体的,启用了匿名登录功能之后,我们所需要做的工作就是从SecurityContext中取出权限主体,然后对其拥有的权限进行校验,不需要每次去检验这个权限主体是否为空了。这样做的好处是我们永远认为请求的主体是拥有权限的,即便他没有登录,系统也会自动为他赋予未登录系统角色的权限,这样后面所有的安全组件都只需要在当前权限主体上进行处理,不用一次一次的判断当前权限主体是否存在。这就更容易保证系统中操作的一致性。“

这种一致性,统一了业务层代码的实现,将匿名用户看做是用户的一种类型,只是访问权限不一样而已。关于它们的优缺点 http://www.mossle.com/docs/auth/html/ch107-anonymous.html 的最后一部分有介绍。

设置登录后的跳转页面,form-login还提供了两个属性default-target-url和always-use-default-target。如果用户进入登录页面是因为要访问某个受限制的资源,当用户登录后,就会回到该访问资源,但是如果不是,default-target-url,就起到作用,指定了登录后的跳转页面,它的默认值是“/”。always-use-default-target的含义就很简单了,是否永远跳转,不管用户之前访问的资源。

处理登出

因为在xml配置中添加了security:logout,所以Spring Security也默认提供了一个登出URL:“/j_spring_security_logout”。

获得用户名

让Spring注入Principal即可

1
2
3
4
5
6
7
8
@Controller
public class HomeController {
  @RequestMapping(value = "/home", method = GET)
  public String home(Principal principal, Model model) {
      model.addAttribute("userName", principal.getName());
      return "home";
  }
}

参考资料:
1.Spring security reference

Spring Web Security 实战 (一) - 最少配置启动

| Comments

你大概应该知道对于应用的安全性(无论是Web应用还是非Web应用),都包含两个方面“身份认证”(authentication)和“权限认证”(authorization,access-control)。身份认证是确立“访问者”所声称的“当事人”(principal)的一个过程(当事人可以是一个用户,设备,或者其他任何要在该应用操作的系统)。权限认证指的是确定该“当事人”是否可以在应用执行某一个操作的过程。

Spring Security能提供的功能亦围绕着这两个核心概念展开。

在“身份认证”方面,Spring Security能提供什么样的支持呢?如下:

HTTP BASIC authentication headers(an IETF RFC-based standard)
HTTP Digest authentication headers(an IETF RFC-based standard)
HTTPX.509 client certificate exchange(an IETF RFC-based standard)
Form-based authentication(for simple user interface needs)
… 更多类型,请参考Spring Security官文。

好吧,概念性的东西就介绍到这,本篇文章以实战为主,下面我们还是来聊聊大家更关心的实现:

在Spring Security 3.0中,相应的功能代码被分到了不同的Jar包中,以清晰的区分不同的功能和第三方依赖。下面介绍三个主要的Jar包:

Core - spring-security-core.jar

包含核心的“身份认证”和“访问控制”类和接口。任何Spring Security的应用都需要这个包,支持独立应用,远程客户端,方法级别(服务层)的安全,以及JDBC,主要包含下面几个顶级包:
org.springframework.security.core
org.springframework.security.access
org.springframework.security.authentication
org.springframework.security.provisioning

Web - spring-security-web.jar

包含对应的filter和web security相关的基础架构代码。任何与Servlet API相关的依赖。如果你需要Spring Security对web身份认证和URL级别的访问控制的支持,就需要它。里面主要包含的包是:org.springframework.security.web。

Config - spring-security-config.jar

包含security命名空间解析代码。如果你要使用XML方式的配置Security,那么你就会需要它。主要包含的包:org.springframework.security.config。

以web应用和xml配置为例,要开始使用Spring Security,需要至少下面的两个依赖,web和config,web会自动依赖于core,所以不用显示的指定core。

1
2
3
4
dependencies {
    compile 'org.springframework.security:spring-security-web:3.2.7.RELEASE'
    compile 'org.springframework.security:spring-security-config:3.2.7.RELEASE'
}

为了能够支持更多的Spring使用,我们以XML方法来配置Security框架。

下面介绍下在Security命名空间下,都有哪些重要的元素节点。

• Web/HTTP Security - 设置filter和相关的Service Bean,以应用框架的用户认证机制,安全化URL,渲染登录,错误页面等。

• Business Object(Method) Security - 用来保证业务层的安全。

• Authentication Manager - 处理身份认证请求

• Access Decision Manager - 为web和method提供访问决策。框架提供了一个默认实现,你也可以自定义一个,语法和普通Bean定义一样。

• AuthenticationProviders - 与Authentication Manager相对应的身份认真机制提供方,提供了多种标准选择,同样也支持自定义。

• UserDetailsService - 与authentication providers联系紧密,也会被其他Bean使用。

接下来,我们开始正式写代码。

首先,你需要在web.xml中定义一个名字是springSecurityFilterChain的filter。它提供了一个“钩子“,来启动Spring Security Web的基础架构。DelegatingFilterProxy是Spring框架中一个类,它代理着一个以Spring Bean方式定义在Spring上下文中的filter。在本例中,bean的名字是springSecurityFilterChain,它是Spring提供的内部基础架构Bean,所以,我们定义的一些自定义的Bean的时候,就不可以使用这个名字springSecurityFilterChain。代码如下:

1
2
3
4
5
6
7
8
9
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

springSecurityFilterChain是由Spring框架定义。

1
2
3
4
5
package org.springframework.security.config.annotation.web.configuration;
public class WebSecurityConfiguration{
  @org.springframework.context.annotation.Bean(name = {"springSecurityFilterChain"})
  public javax.servlet.Filter springSecurityFilterChain() throws java.lang.Exception { /* compiled code */ }   
}

做完上面的”钩子”配置,你就可以正式开始Spring上下文的定义。首先来配置Web/HTTP Security节点部分“http”:

1
2
3
4
5
<http use-expressions="true">
    <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
    <form-login/>
    <logout/>
</http>

上面是使得Spring Security工作的最小http节点配置。安全拦截所有应用中的URL,要求访问者(“当事人”)含有ROLE_USER权限,要求使用带有用户名和密码的表单登录,并提供一个登出的URL。上面的pattern可以使用正则表达式来匹配对应路径,access中的hasRole必须配合use-expressions=“true”才能使用(4.0以上有所不同)。如果提供多种权限,ROLE_USER后面可以以逗号分隔以允许多种权限(如果是4.0以上版本,Spring会自动给hasRole中的值加上ROLE_作为前缀,所以需要注意)。

这里允许定义多个intercept-url以应对不同的url,比如,css,js的访问,自定义登陆页面的访问,等。由于该intercept-url会根据定义的先后顺序来解析输入的url,所以,越明确的url模式,越应该放在前面。

note:如果你对filter比较熟悉,你应该能明白intercept-url其实是在配置FilterChainProxy所使用的filter。

接下来,需要配置Authentication Manager:

1
2
3
4
5
6
7
<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="ben" password="ben" authorities="ROLE_USER"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

authentication-provider和user-service分别创建了两个bean,DaoAuthenticationProvider和InMemoryDaoImpl。它们作为authentication-manager的子元素存在,而authentication-manager则创建了一个ProviderManager的Bean,并将authentication-provider注册到该ProviderManager。理解这段代码的含义很简单,一个用户ben,密码ben,拥有ROLE_USER这个权限。

你还可以通过user-service上的properties属性来指定一个properties文件作为用户定义的输入。

到此,你就完成了一个最简单的Spring Web Security的配置。拦截所有url,需要输入用户名和密码登录访问。

参考资料:
1. Spring Security Reference。