<Web> spring security翻译

    1. 第一步就是创建java配置,配置就是创建一个Servlet Filter,也就是springSecurityFilterChain,它负责处理应用中的所有spring security事物(保护url,验证用户名密码,重定向到登录表单)。

      configureGlobal方法名不重要,重要的是需要在有@EnableWebSecurity、@EnableGlobalMethodSecurity、@EnableGlobalAuthentication等注解的类下配置AuthenticationManagerBuilder,否则导致不可预知的结果。
      上面这个配置看起来没多少,但是做的工作却很多,如下:
      1. 要求认证每一个url
      2. 为你生成一个登录表单
      3. 允许用户通过user、password来通过验证
      4. 允许用户登出
      5. 防止CSRF攻击
      6. 固定Session保护
      7. 集成Security Header
      8. 集成Servlet API method
    2. 第二步注册springSecurityFilterChain,这个在servlet 3.0环境下可以通过Spring's WebApplicationInitializer support来做java配置。当然,Spring Security提供了一个基类AbstractSecurityWebApplicationInitializer,它将保证springSecurityChain被注册。但是使用AbstractSecurityWebApplicationInitializer的时候要分两种情况:
      1. 项目中没有使用到springMVC或Spring而想用SpringSecurity的话,你需要传递Security Config到父类中,来保证配置被加载。如下:

        这里做了以下事情:
        1. 自动注册springSecurityFilterChain Filter
        2. 添加ContextLoadListener来加载Security Config
      2. 如果已经使用了springMVC,我们只需要这样:
    3. 到现在我们的Spring Security只知道如何去验证用户,那么如何让它知道我们需要验证所有用户呢?如何让它知道我们需要基于表单的验证呢?这个问题在于WebSecurityConfigurerAdapter的configure方法中,它提供了一个HttpSecurity来帮助我们配置相应需求:

      默认的配置如下:
      1. 保证到应用的请求都必须被验证
      2. 允许用户通过基于表单的登录验证
      3. 允许用户验证Http的基本验证
      4. 要注意这里的and()方法,它让我们可以更好的配置http,看源码就可以知道。
    4. 可以注意到登录表单是从哪来的,因为还没有提到过任何html或jsp呢,由于spring Security的默认配置没有明确地为登录页面设置url,spring Security会自动生成一个,但是一般是自定义的登录界面,那么可以通过controller来指向它:
    5. 我们现在只是要求用户被验证,并且每一个url也被验证。我们可以指定为url子目录添加特别的需求:

      如上就是为http提供了多个子验证模块
    6. 结构和实现
      1. 核心组件:
      2. SecurityContextHolder--这是最基本的对象,我们在这里存储应用的Security Context,包涵了应用中用户的安全验证细节。SecurityContextHolder默认使用ThreadLocal(使得每一个线程保持各自独立的对象,通过set来设置,get来获取)来存储这些细节,这就意味着在该线程中,security context可以被每一个方法访问到,即使Security contex没有作为参数被明确传递给该方法。这样但处理完request的时候,清空线程也会是安全的,当然这都不需要使用者来关心。但是有些应用不太适合用ThreadLocal,因为它们有特定的使用线程方式,比如一个Swing的客户端需要让JVM中的所有线程使用相同的Security context。启动时,可以通过策略来指定你需要的SecurityContextHolder存储方式。然而大多数情况下,我们是不需要修改默认存储策略的。
      3. Authentication--在SecurityContextHolder中我们存储的是和应用用户交互的信息,spring security使用一个Authentication对象来表述这信息,你不需要自己创建这个对象,但是你经常需要查看这个对象,你可以在你的应用中使用下面这段代码来获取当前被认证的用户:

        这个对象可以通过调用SecurityContext的getContext()来获取,这个对象是放在threadlocal里面的。
      4. UserDetailsService--从上面一段代码中,还有一点值得我们注意的是Authentication中包涵了用户,这个用户是一个Object类型,大多数情况下这个类可以被强转成UserDetails对象,UserDetails是spring security的核心接口UserDetails,它表述的是一个用户信息,但是是可扩展的并且是基于应用特定的。将UserDetails看作是一个数据库适配器,spring security想要从SecurityContextHolder中获取什么信息,就找UserDetails就行了,作为一个用户数据库,你一般会将UserDetails再次强转成你的用户对象,这样你就可以调用特定的业务方法,如:getEmail(),getUserId()等等。
        现在,你可能会疑惑,什么时候提供UserDetails呢,我怎么提供呢,答案就是UserDetailsService,这个接口有一个唯一的方法就是通过获取一个用户名字符串参数来返回UserDetails:

        这个是在spring security中获取用户信息最常用的方法。
        一旦成功验证用户,那么UserDetails就会被用于在SecurityContextHolder中创建一个Authentication对象,好消息是我们已经有了一系列的UserDetailsService实现类,包括使用了内存map(InMemoryDaoImpl)和使用JDBC的(JdbcDaoImpl),有很多人喜欢用他们自己的实现类,他们自己的实现其实也只基于他们自己的用户系统而已,
      5. GrantedAuthority--除了用户信息,Authentication还有一个重要的方法getAuthorities(),它用来提供一个GrantedAuthority对象数组,GrantedAuthority是授权给用户的权限,也被叫做角色
    7. 认证
      1. spring security到底干了什么
        1. 保证用户操作是登录过的
        2. 用户名密码是正确的
        3. 用户的上下问信息是被包涵的
        4. 用户的安全上下文是被创建的
        5. 权限是被授予的
      2. 现在我们来复现一下认证过程:
        1. 你访问一个网站的首页,点击连接
        2. 请求发送到服务器,服务器发现这是个被保护的资源
        3. 由于你没有被授权,服务器需要将你没有权限的信息返回给你,可以http的错误信息,也可以是自定义的错误页面
        4. 根据验证机制,你的浏览器会重定向到一个登录页面以便你完成登录信息填写,或者浏览器会直接填写你的表单(cookie)
        5. 浏览器再次发送给服务器,信息既可以是用户POST的表单信息,也可以是包涵你验证细节的http头部
        6. 然后服务器会检测当前验证信息是否正确,如果正确,会进入第7步,如果错误,一般会返回错误信息以便再次填写表单
        7. 会再次请求你最开的连接,如果你的足够的权限来访问到这个资源,那么请求成功,如果没有,你会收到403错误信息
      3. 上面顺序的绝大部分spring security都提供了相应的类来对应处理,主要有ExceptionTranslationFilter,AuthenticationEntryPoint和AuthenticationManager的验证机制。
        1. ExceptionTranslationFilter--是一个spring security的过滤器,用来检测spring security的任何异常抛出,异常主要由AbstractSecurityInterceptor抛出,它是验证服务的主要提供者。
        2. AuthenticationEntryPoint--是认证过程中的第三步,每个应用都会提供它自己的认证机制,那么每一个认证机制有他们自己的AuthenticationEntryPoint实现。
        3. 认证机制--一旦你的浏览器提交你的认证信息,也就是第六步,当验证机制取回所有Authentication对象,如果验证成功,会将Authentication对象放入SecurityContextHolder,然后再次启动源请求,否则AuthenticationManager拒绝请求,验证机制会向浏览器要求再次发送认证信息
      4. 在请求中间存储SecurityContext,略
    8. 本地化
      1. spring security支持异常信息的本地化,以便用户更好地理解,如果你的应用是为英语用户提供的,那就不需要了。
      2. 所有的异常信息都可以被本地化,包括认证失败和授权失败,异常和日志信息
    9. 核心服务
      1. AuthenticationManager--是一个接口,我们可以实现任何我们想做的事,实际上如何操作?如果我们需要检查多个验证数据库呢?默认实现是ProviderManager,比起自己处理认证请求,它将这部分工作移交给了一系列的AuthencationProvider,每一个都会被用于验证,每一个Provider都可抛出异常,也可以返回一个完全填充的Authencation对象,还记的UserDetails和UserDetailsService吗?如果不记得就返回去看看。验证请求最通常的做法就是获取到相应的UserDetails并检查获取到的密码和用户输入的密码,这就是AuthencationProvider的做法,而UserDetails对象和他的权限组会被完全填充到Authencation对象中,并保存在SecurityContext中。如果是基于namespace的配置,可以如下:

        在这里,我们提供了3个Provider,他们会按显示的顺序来验证,如果或者通过return null 来跳过验证,如果所有Provider都返回null,那么ProviderManager会返回一个ProviderNotFoundException,你们可以到ProviderManager的JavaDoc中查看更详细的关于provider chain的信息。
      2. ProviderManager--验证机制会在filter会注入一个ProviderManager的引用,并调用它来验证请求。你需要的provider是可以互换的,默认地,spring security会通过ProviderManager来清空Authencation对象中任何敏感的认证信息,防止像密码这样的信息存在的时间大于它可作用的时间。这样做会带来一个问题:如果在Authencation中使用了缓存,缓存中有UserDetails对象的引用,那么移除它的认证信息,那么这个对象就不能再次验证对象中的缓存了,你需要将它复制一份到你的缓存中。还有一种办法就是ProviderManager.eraseCredentialsAfterAuthentication()设为false
      3. DaoAuthenticationProvider--这是spring security提供的AuthencationProvider接口最简单的实现类,也是被最容易被框架兼容的。它利用一个UserDetailsService(作为DAO)来查找用户名,密码,权限组,它只是简单地验证从用户提交信息中包装成的UsernamePasswordAuthenticaionToken中的密码和UserDetailsService中的密码,配置也很简单:
      4. UserDetailsService--上面已经提到过,所有Provider都要用到UserDetailsService接口和UserDetails,UserDetailsService中只有唯一的一个方法,就是UserDetails loadUserByUsername(String username) throws UsernameNotFoundException。
      5. In-Memory Authentication--用来创建一个自定义的UserDetailsService实现类,选择从持久层获取信息,但是大多数应用无需这么复杂。
      6. JdbcDaoImpl--spring security也包括一个从jdbc数据源来认证的UserDetailsService,内部使用了springJDBC,就避免了复杂的ORM来存储用户信息,如果你的应用用到了ORM工具,你就需要自己来实现UserDetailsService来重用或许已经创建好的映射文件:

        通过修改DriverManagerDataSource信息,你可以使用不同的关系数据库。
      7. 密码编码
        1. spring security的PasswordEncoding接口用来支持持久化密码时的编码工作,我们绝不应该直接将原文密码直接持久化,一般都是利用单向哈希算法(将任意长度的二进制值映射成一个固定长度的二进制值,这个小的二进制值被称为哈希值,它是一段数据的唯一表现形式,要找到两个散列为同一个值的输入,在计算上几乎不可能,所以它可以用来快速查找和加密),如bcrypt,
posted @ 2015-05-18 14:26  丶千纸鸢  阅读(275)  评论(0编辑  收藏  举报