NO END FOR LEARNING

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

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

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