NO END FOR LEARNING

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

Spring Web Security 实战 (四) - 了解Security基础架构

| Comments

在了解了Spring Security的基本使用之后,还是需要更深入的了解Spring Security的基础架构,核心组件等。

Spring Security是以自包含的角度来实现安全设计,所以使用Spring Security没有任何侵入性。因为Spring Security的目标是自己容器内管理, 所以不需要为你的Java运行环境进行什么特别的配置。

核心组件

SecurityContextHolder, SecurityContext和Authentication对象

SecurityContextHolder,就如该类的名字所描述的那样,它用来存放当前应用程序的当前安全上下文中的细节,当然也包括“当事人Principal”的细节。SecurityContextHolder将上下文信息存储在ThreadLocal当中,这意味着,安全环境在同一个线程执行的方法一直是有效的。这种情况下使用ThreadLocal是非常安全的,只要记得在处理完当前主体的请求以后,把这个线程清除就行了。当然,Spring Security自动帮你管理这一切了,你就不用担心什么了。

下面这段代码很清晰的说明了上面的实现策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
  private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

  public SecurityContext getContext() {
      SecurityContext ctx = contextHolder.get();

      if (ctx == null) {
          ctx = createEmptyContext();
          contextHolder.set(ctx);
      }

      return ctx;
  }
}

当你从SecurityContextHolder中拿到SecurityContext后,你就可以通过下面的这段代码取到“当事人”或者说用户的信息:

1
2
3
4
5
6
7
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

看完上面的代码,就要引入第二个关键组件Authentication:

Authentication是一个接口,代表着一种“认证”令牌,它里面存放着“当事人”的基本信息和是否认证的标志信息。框架在接受到认证请求时,会将新创建的Authentication对象会传递给AuthenticationManager.authenticate(Authentication),由它来决定“认证”是否成功。

而一旦“认证”请求通过,Authentication通常都会存储在SecurityContext中,由SecurityContextHolder来管理。

除了主体,另一个Authentication提供的重要方法是getAuthorities()。 这个方法提供了GrantedAuthority对象数组。 毫无疑问,GrantedAuthority是赋予到主体的权限。这些权限通常使用角色表示,比如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。

SecurityContextHolder,提供访问SecurityContext的方式。
SecurityContext,保存Authentication信息,和请求对应的安全信息。
Authentication,作为认证的令牌。
GrantedAuthority,反应在应用程序范围内,赋予的权限。

Spring Security的认证(Authentication)过程

让我们考虑一种标准的验证场景,每个人都很熟悉的那种。
一个用户想使用一个账号和密码进行登陆。
系统(成功的)验证了密码对于这个用户名是正确的。
这个用户对应的信息获取(他们的角色列表以及等等)。
为用户建立一个安全环境。
用户会执行一些操作,这些都是潜在被权限控制机制所保护的,通过对操作的授权,使用当前的安全环境信息。

前三个项目执行了验证过程,所以我们可以看一下Spring Security的作用。
用户名和密码被获得,并进行比对,在一个UsernamePasswordAuthenticationToken的实例中(它是Authentication接口的一个实例,我们在之前已经见过了)。
这个标志被发送给一个AuthenticationManager的实例进行校验。
AuthenticationManager返回一个完全的Authentication实例,在成功校验后。
安全环境被建立,通过调用SecurityContextHolder.getContext().setAuthentication(…),传递到返回的验证对象中。

事实上,Spring Security并不介意你如何将Authentication对象放到SecurityContextHolder里。唯一关键的一点是 在AbstractSecurityInterceptor需要验证一个用户操作之前,SecurityContextHolder必须包含了一个表示了主体的Authentication。

待续。。。

Spring Web Security 实战 (三) - 改变用户认证方式和安全性

| Comments

之前实现的表单提交方式来验证用户,实际上是不安全,http请求没有做任何的处理,表单内容以明文方式发送,在浏览器查看请求,得到结果就如下:

j_username:ben
j_password:ben
submit:Login

而且,用户名和密码保存在xml或者properties方式保存在服务器端,也是不合理的。

Http Basic

参考:http://blog.itpub.net/23071790/viewspace-709367/

“在HTTP协议进行通信的过程中,HTTP协议定义了基本认证过程以允许HTTP服务器对WEB浏览器进行用户身份证的方法,当一个客户端向HTTP服务 器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法。客户端在接收到HTTP服务器的身份认证要求后,会提示用户输入用户名及密码,然后将用户名及密码以BASE64加密,加密后的密文将附加于请求信息中, 如当用户名为anjuta,密码为:123456时,客户端将用户名和密码用“:”合并,并将合并后的字符串用BASE64加密为密文,并于每次请求数据 时,将密文附加于请求头(Request Header)中。HTTP服务器在每次收到请求包后,根据协议取得客户端附加的用户信息(BASE64加密的用户名和密码),解开请求包,对用户名及密码进行验证,如果用 户名及密码正确,则根据客户端请求,返回客户端所需要的数据;否则,返回错误代码或重新要求客户端提供用户名及密码。”

1
2
3
4
<security:http>
    <security:intercept-url pattern="/**" access="ROLE_USER"/>
    <security:http-basic/>
</security:http>

这种方式直接的表单要好点,但其实也不安全,用户名和密码可以通过BASE64反编码得到。

使用数据库

如果你想使用数据库作为身份认证的数据来源,非常简单,只需要配置一个DataSource即可。

1
2
3
4
5
<authentication-manager>
  <authentication-provider>
      <jdbc-user-service data-source-ref="securityDataSource"/>
  </authentication-provider>
</authentication-manager>

这里的securityDataSource是Spring上下文种一个DataSource Bean,它指向一个含有Spring Security“标准用户数据库表”的数据库。

除了直接配置DataSource,你还可以配置一个Spring Security的JdbcDaoImpl的Bean,如下:

1
2
3
4
5
6
7
<authentication-manager>
  <authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

<beans:bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
  <beans:property name="dataSource" ref="dataSource"/>
</beans:bean>

你肯定会问,这个“标准用户数据库表”的结构是什么样的。

默认的结构是有两张表:”users”和”authorities”。Users表有三个字段username,password,enabled。Authorities表有两个字段username,authority

如果你不满意标准的表结构,你还可以实现AuthenticationProvider接口自定义实现。

1
2
3
<authentication-manager>
  <authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>

添加Password编码器

将密码以明文的方式存放在代码中或者是数据库中,始终是不安全的,AuthenticationProvider允许设置密码的加密编码。并且应该使用为此目的而设计的标准的安全hash算法,而不是一般的SHA或者MD5哈希算法。

1
2
3
4
5
6
7
8
9
10
11
<bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
    <authentication-provider>
        <password-encoder ref="bcryptEncoder"/>
        <user-service>
            <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
                  authorities="ROLE_USER, ROLE_ADMIN"/>
            <user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f" authorities="ROLE_USER"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

Bcrypt是Spring文档里推荐的hash算法,它是一个跨平台的文件加密工具。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。(来自-百度百科)

HTTPS

很多情况下我们依旧需要采用以表单的方式来实现身份认真,就必须要避免,表单数据,即用户名和密码在HTTP协议下以明文传输。

Spring Security可以非常方面的支持要求某些URL必须是在https协议下才可以访问。

1
2
3
4
5
<http>
<intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
...
</http>

上面这段代码,当用户以http协议访问/secure/**,就会先跳转到https上。

参考资料:Spring Security Reference