访问项目域弹出浏览器原生登录框----Spring Security登陆认证 LDAP认证

原文链接

http://www.cnblogs.com/hzhuxin/archive/2011/12/14/2287363.html

http://www.yeolar.com/note/2011/10/19/spring-security/

http://www.linuxeden.com/html/news/20130915/143586.html

http://chen-rojer-gmail-com.iteye.com/blog/1037973

1.springSecurity的登录验证:

springSecurity的登录验证是由org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter这个过滤器来完成的,在该类的父类AbstractAuthenticationProcessingFilter中有一个AuthenticationManager接口属性,验证工作主要是通过这个AuthenticationManager接口的实例来完成的。在默认情况下,springSecurity框架会把org.springframework.security.authentication.ProviderManager类的实例注入到该属性,

AuthenticationManager接口的相关类图如下:

UsernamePasswordAuthenticationFilter的验证过程如下:

1. 首先过滤器会调用自身的attemptAuthentication方法,从request中取出authentication, authentication是在org.springframework.security.web.context.SecurityContextPersistenceFilter过滤器中通过捕获用户提交的登录表单中的内容生成的一个org.springframework.security.core.Authentication接口实例.

2. 拿到authentication对象后,过滤器会调用ProviderManager类的authenticate方法,并传入该对象

3.ProviderManager类的authenticate方法再调用自身的doAuthentication方法,在doAuthentication方法中会调用类中的List<AuthenticationProvider> providers集合中的各个AuthenticationProvider接口实现类中的authenticate(Authentication authentication)方法进行验证,由此可见,真正的验证逻辑是由各个各个AuthenticationProvider接口实现类来完成的,DaoAuthenticationProvider类是默认情况下注入的一个AuthenticationProvider接口实现类

4.AuthenticationProvider接口通过UserDetailsService来获取用户信息

以下为时序图:


spring-security用户权限认证框架

Yeolar  2011-10-19 13:15  

spring中没有提供默认的权限管理模块,而是基于Acegi开发了一个spring-security,它是基于spring的用户权限认证框架。spring-security是一个比较轻量的权限认证框架,它没有实现管理界面,只给出了相应的接口。

配置spring-security

在web.xml配置文件中添加过滤器:

<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>

然后在<classpath>路径下创建配置文件PROJECT-security.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-2.0.xsd">

    <http auto-config="true" access-denied-page="/access_denied.jsp">
        <intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
        <intercept-url pattern="/user/**" access="ROLE_USER" />

        <form-login login-page="/login.htm" authentication-failure-url="/login.htm?error=1" default-target-url="/" />
        <remember-me data-source-ref="dataSource" />
        <logout invalidate-session="true" logout-success-url="/" />
        <!--
        Uncomment to enable X509 client authentication support
        <x509 />
          -->
    </http>

    <authentication-provider>
        <!--
        <password-encoder hash="md5" />
          -->
        <jdbc-user-service data-source-ref="dataSource"
            users-by-username-query="select account as username, password, status as enabled from user where account=?"
            authorities-by-username-query="select account as username, authority from user where account=?" />
    </authentication-provider>
</beans:beans>

同时将该配置文件加到web.xml的 <context-param> 里。

spring-security中使用角色来分类管理用户权限,如上面的配置中就包含了ROLE_ADMIN和ROLE_USER两个角色,并分别有/admin/和/user/的URL路径下的权限。

在数据库中保存用户帐号

用户的帐号密码有几种不同的方式保存,包括xml中、LDAP和数据库中等。上面使用的是保存到数据库中的方式,使用了之前在applicationContext.xml中配置的dataSource bean。使用数据库保存帐号时,需要按照spring-security规定的字段来建表,有两个相关的表,分别用于保存帐号密码和登录状态。使用MySQL可以这样创建:

CREATE TABLE `user` (
  `account` varchar(50) NOT NULL,
  `password` varchar(50) NOT NULL,
  `authority` varchar(50) NOT NULL,
  `status` tinyint(1) NOT NULL,
  UNIQUE KEY `account` (`account`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

加密用户密码

默认spring-security中采用明文方式存储密码,可以通过设置 <password-encoder> 来对密码加密。这时对应的用户注册模块也要将密码以加密后的数据保存到数据库中才行。

import org.springframework.security.providers.encoding.Md5PasswordEncoder;
import org.springframework.security.providers.encoding.PasswordEncoder;

PasswordEncoder encoder = new Md5PasswordEncoder();
String password = encoder.encodePassword(form.getPassword(), null);

阻止用户重复登录

可以通过会话控制来防止用户重复登录,这可以通过配置来实现。首先在web.xml中添加监听:

<listener>
    <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>

然后在PROJECT-security.xml配置文件中的 <http></http> 内添加:

<concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />

max-sessions="1" 表示该用户同时登录的最大会话数为1,exception-if-maximum-exceeded="true" 表示阻止超出的用户登录。

在jsp中加入spring-security

spring-security给出了在jsp中使用的接口。用户登录可以使用下面的表单:

<form name='f' action='/PROJECT/j_spring_security_check' method='POST'>
<table>
  <tr><td>用户名:</td><td><input type='text' name='j_username' value=''></td></tr>
  <tr><td>密码:</td><td><input type='password' name='j_password'/></td></tr>
  <tr><td></td><td><input type='checkbox' name='_spring_security_remember_me'/> 自动登录</td></tr>
  <tr><td colspan='2' align="right"><input name="reset" id="reset" type="reset" value="重置" /> <input name="submit" id="submit" type="submit" value="登录" /></td></tr>
</table>
</form>

根据登录用户进行条件判断可以使用下面的方式:

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<sec:authorize ifAllGranted="ROLE_ANONYMOUS">
<!-- ... -->
</sec:authorize>
<sec:authorize ifAllGranted="ROLE_USER">
<!-- ... -->
</sec:authorize>

在特定jsp页面获取登录用户的帐号的方法是:

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<input name="customer" type="hidden" value="<sec:authentication property='principal.username' />" />

spring-security还有很多相关的用法,可以查看 官方的文档



2.使用 Spring Security构建HTTP基本认证示例

来源:开源中国社区 作者:oschina
  

HTTP基本认证(BA)是一个简单的认证机制。当一个web客户端请求任何受保护的web资源,服务器端就发送回401(未授权)状态码的HTTP响应,响应中还包括WWW-Authenticate HTTP 头部,如WWW-Authenticate: Basic realm="realm here". 与此同时,浏览器会弹出一个登录对话框,并在对话框中提示输入用户名和密码。

下面的例子会展示如何使用Spring Security框架配置HTPP基本认证

文章中使用到的工具和技术:

  1. Spring Framework 3.1.4
  2. Spring Security 3.1.4
  3. Spring Tool Suite 3.2
  4. JDK 1.6
  5. Tomcat 7

我们在之前的文章( Spring Security 3 Hello World Example)的基础上做点修改, 来讲述如何配置HTTP基本认证。

注意:如果web客户端和服务端的连接不是安全的,那么对于用户认证来说,HTTP基本认证体系不是一个安全的方法。在传输过程中用户凭证是用BASE64编码的,但是没有加密或哈希。所以截获用户凭证是有一点概率的。而基本认证则可以在HTTPS下使用。

1.修改Spring Security配置

配置HTTP基础认证只需要在Spring Security 配置xml中加入<http-basic/>就行了。

文件:WEB-INF/spring-security.xml

 

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-3.1.xsd">
 
    <http>
        <intercept-url pattern="/secured/*" access="ROLE_USER" />
         
        <!-- Adds Support for basic authentication -->
        <http-basic/>
    </http>
 
    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="srccodes" password="password" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
 
</beans:beans>

2.整个项目的架构

Overall Project Structure

3.Demo

启动服务器并且部署web应用。尝试打开URLhttp://:/spring-security-http-basic-authentication/secured/mypage.

服务器发送的HTTP 响应头

 

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html;charset=utf-8
Content-Length: 981
Date: Mon, 09 Sep 2013 10:47:14 GMT
浏览器将会打开认证对话框,提示输入用户名和密码。

HTTP basic authentication dialog

对于错误的凭据,下面的认证失败信息也将会被展示出来。

authentication failure message

对于正确的用户名(srccodes)和密码(password),你就将能够看到受保护的页面了。

view the secured page

发送给服务的HTTP请求头

GET /spring-security-http-basic-authentication/secured/mypage HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cookie: JSESSIONID=896331E26095C95449516FCBF2E0E93C; __atuvc=28%7C31%2C0%7C32%2C0%7C33%2C215%7C34%2C59%7C35
Authorization: Basic c3JjY29kZXM6cGFzc3dvcmQ=
注意: 'c3JjY29kZXM6cGFzc3dvcmQ=‘是‘用户名:密码’的Base64编码版本,如:‘srccodes:password’。

注意:基础认证并不提供任何注销功能。关闭浏览器就能退出了。

下载 SrcCodes

spring-security-http-basic-authentication:GitHub or zip

引用

英文原文:Spring Security HTTP Basic Authentication Example

参与翻译(2人)徐继开,LeoXu

文章转载自:开源中国社区 [http://www.oschina.net]
本文标题:libnode 0.3.0 发布,C++ 语言版的 Node.js
本文地址:使用 Spring Security 构建一个 HTTP 基本认证示例

 

 3.Spring Security 3 自定义认证,授权示例


时间:2013-09-15 08:46来源:开源中国社区 作者:oschina责任编辑:zhangkai


Spring Security 3.x出来一段时间了,跟Acegi是大不同了,与2.x的版本也有一些小小的区别,网上有一些文档,也有人翻译Spring Security 3.xguide,但通过阅读guide,无法马上就能很容易的实现一个完整的实例。

 

我花了点儿时间,根据以前的实战经验,整理了一份完整的入门教程,供需要的朋友们参考。

1,建一个web project,并导入所有需要的lib,这步就不多讲了。

2,配置web.xml,使用Spring的机制装载:

 

Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"  
  3.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.      xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   
  5.      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  6.     <context-param>  
  7.         <param-name>contextConfigLocation</param-name>  
  8.         <param-value>classpath:applicationContext*.xml</param-value>  
  9.     </context-param>  
  10.   
  11.     <listener>  
  12.         <listener-class>  
  13.              org.springframework.web.context.ContextLoaderListener  
  14.         </listener-class>  
  15.     </listener>  
  16.   
  17.     <filter>  
  18.         <filter-name>springSecurityFilterChain</filter-name>  
  19.         <filter-class>  
  20.              org.springframework.web.filter.DelegatingFilterProxy  
  21.         </filter-class>  
  22.     </filter>  
  23.     <filter-mapping>  
  24.         <filter-name>springSecurityFilterChain</filter-name>  
  25.         <url-pattern>/*</url-pattern>  
  26.     </filter-mapping>  
  27.   
  28.   
  29.     <welcome-file-list>  
  30.         <welcome-file>login.jsp</welcome-file>  
  31.     </welcome-file-list>  
  32. </web-app>  

 这个文件中的内容我相信大家都很熟悉了,不再多说了。

 

 

2,来看看applicationContext-security.xml这个配置文件,关于SpringSecurity的配置均在其中:

 

Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  3.      xmlns:beans="http://www.springframework.org/schema/beans"  
  4.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.      xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.             http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.             http://www.springframework.org/schema/security  
  8.             http://www.springframework.org/schema/security/spring-security-3.0.xsd">  
  9.   
  10.     <http access-denied-page="/403.jsp"><!-- 当访问被拒绝时,会转到403.jsp -->  
  11.         <intercept-url pattern="/login.jsp" filters="none" />  
  12.         <form-login login-page="/login.jsp"  
  13.              authentication-failure-url="/login.jsp?error=true"  
  14.              default-target-url="/index.jsp" />  
  15.         <logout logout-success-url="/login.jsp" />  
  16.         <http-basic />  
  17.         <!-- 增加一个filter,这点与Acegi是不一样的,不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->  
  18.         <custom-filter before="FILTER_SECURITY_INTERCEPTOR"  
  19.              ref="myFilter" />  
  20.     </http>  
  21.   
  22.     <!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,  
  23.      我们的所有控制将在这三个类中实现,解释详见具体配置 -->  
  24.     <beans:bean id="myFilter" class="com.robin.erp.fwk.security.MyFilterSecurityInterceptor">  
  25.         <beans:property name="authenticationManager"  
  26.              ref="authenticationManager" />  
  27.         <beans:property name="accessDecisionManager"  
  28.              ref="myAccessDecisionManagerBean" />  
  29.         <beans:property name="securityMetadataSource"  
  30.              ref="securityMetadataSource" />  
  31.     </beans:bean>  
  32.       
  33.     <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->  
  34.     <authentication-manager alias="authenticationManager">  
  35.         <authentication-provider  
  36.             user-service-ref="myUserDetailService">  
  37.             <!--    如果用户的密码采用加密的话,可以加点“盐”  
  38.                  <password-encoder hash="md5" />  
  39.             -->  
  40.         </authentication-provider>  
  41.     </authentication-manager>  
  42.     <beans:bean id="myUserDetailService"  
  43.          class="com.robin.erp.fwk.security.MyUserDetailService" />  
  44.   
  45.     <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->  
  46.     <beans:bean id="myAccessDecisionManagerBean"  
  47.          class="com.robin.erp.fwk.security.MyAccessDecisionManager">  
  48.     </beans:bean>  
  49.       
  50.     <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 -->  
  51.     <beans:bean id="securityMetadataSource"  
  52.          class="com.robin.erp.fwk.security.MyInvocationSecurityMetadataSource" />  
  53.   
  54. </beans:beans>  

 3,来看看自定义filter的实现:

Java代码   收藏代码
  1. package com.example.spring.security;  
  2. import java.io.IOException;  
  3.   
  4. import javax.servlet.Filter;  
  5. import javax.servlet.FilterChain;  
  6. import javax.servlet.FilterConfig;  
  7. import javax.servlet.ServletException;  
  8. import javax.servlet.ServletRequest;  
  9. import javax.servlet.ServletResponse;  
  10.   
  11. import org.springframework.security.access.SecurityMetadataSource;  
  12. import org.springframework.security.access.intercept.AbstractSecurityInterceptor;  
  13. import org.springframework.security.access.intercept.InterceptorStatusToken;  
  14. import org.springframework.security.web.FilterInvocation;  
  15. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
  16.   
  17. public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor  
  18.         implements Filter {  
  19.   
  20.     private FilterInvocationSecurityMetadataSource securityMetadataSource;  
  21.   
  22.     // ~ Methods  
  23.     // ========================================================================================================  
  24.   
  25.     /** 
  26.       * Method that is actually called by the filter chain. Simply delegates to 
  27.       * the {@link #invoke(FilterInvocation)} method. 
  28.       *  
  29.       * @param request 
  30.       *             the servlet request 
  31.       * @param response 
  32.       *             the servlet response 
  33.       * @param chain 
  34.       *             the filter chain 
  35.       *  
  36.       * @throws IOException 
  37.       *              if the filter chain fails 
  38.       * @throws ServletException 
  39.       *              if the filter chain fails 
  40.      */  
  41.     public void doFilter(ServletRequest request, ServletResponse response,  
  42.              FilterChain chain) throws IOException, ServletException {  
  43.          FilterInvocation fi = new FilterInvocation(request, response, chain);  
  44.          invoke(fi);  
  45.      }  
  46.   
  47.     public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {  
  48.         return this.securityMetadataSource;  
  49.      }  
  50.   
  51.     public Class<? extends Object> getSecureObjectClass() {  
  52.         return FilterInvocation.class;  
  53.      }  
  54.   
  55.     public void invoke(FilterInvocation fi) throws IOException,  
  56.              ServletException {  
  57.          InterceptorStatusToken token = super.beforeInvocation(fi);  
  58.         try {  
  59.              fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
  60.          } finally {  
  61.             super.afterInvocation(token, null);  
  62.          }  
  63.      }  
  64.   
  65.     public SecurityMetadataSource obtainSecurityMetadataSource() {  
  66.         return this.securityMetadataSource;  
  67.      }  
  68.   
  69.     public void setSecurityMetadataSource(  
  70.              FilterInvocationSecurityMetadataSource newSource) {  
  71.         this.securityMetadataSource = newSource;  
  72.      }  
  73.   
  74.      @Override  
  75.     public void destroy() {  
  76.      }  
  77.   
  78.      @Override  
  79.     public void init(FilterConfig arg0) throws ServletException {  
  80.      }  
  81.   
  82. }  

 最核心的代码就是invoke方法中的InterceptorStatusTokentoken = super.beforeInvocation(fi);这一句,即在执行doFilter之前,进行权限的检查,而具体的实现已经交给accessDecisionManager了。

4,来看看authentication-provider的实现:

Java代码   收藏代码
  1. package com.example.spring.security;  
  2. import java.util.ArrayList;  
  3. import java.util.Collection;  
  4.   
  5. import org.springframework.dao.DataAccessException;  
  6. import org.springframework.security.core.GrantedAuthority;  
  7. import org.springframework.security.core.authority.GrantedAuthorityImpl;  
  8. import org.springframework.security.core.userdetails.User;  
  9. import org.springframework.security.core.userdetails.UserDetails;  
  10. import org.springframework.security.core.userdetails.UserDetailsService;  
  11. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  12.   
  13. public class MyUserDetailService implements UserDetailsService {  
  14.   
  15.      @Override  
  16.     public UserDetails loadUserByUsername(String username)  
  17.             throws UsernameNotFoundException, DataAccessException {  
  18.          Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();  
  19.          GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");  
  20.          auths.add(auth2);  
  21.         if(username.equals("robin1")){  
  22.              auths=new ArrayList<GrantedAuthority>();  
  23.              GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_ROBIN");  
  24.              auths.add(auth1);  
  25.          }  
  26.           
  27. //         User(String username, String password, boolean enabled, boolean accountNonExpired,  
  28. //                     boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities) {  
  29.          User user = new User(username,  
  30.                 "robin"truetruetruetrue, auths);  
  31.         return user;  
  32.      }  
  33.       
  34. }  
 

 

在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等,我想这么简单的代码就不再多解释了。

 

5,对于资源的访问权限的定义,我们通过实现FilterInvocationSecurityMetadataSource这个接口来初始化数据。

Java代码   收藏代码
  1. package com.example.spring.security;  
  2. import java.util.ArrayList;  
  3. import java.util.Collection;  
  4. import java.util.HashMap;  
  5. import java.util.Iterator;  
  6. import java.util.Map;  
  7.   
  8. import org.springframework.security.access.ConfigAttribute;  
  9. import org.springframework.security.access.SecurityConfig;  
  10. import org.springframework.security.web.FilterInvocation;  
  11. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
  12. import org.springframework.security.web.util.AntUrlPathMatcher;  
  13. import org.springframework.security.web.util.UrlMatcher;  
  14. /** 
  15.  
  16. * 此类在初始化时,应该取到所有资源及其对应角色的定义 
  17.  
  18. * @author Robin 
  19.  
  20. */  
  21. public class MyInvocationSecurityMetadataSource  
  22.         implements FilterInvocationSecurityMetadataSource {  
  23.     private UrlMatcher urlMatcher = new AntUrlPathMatcher();;  
  24.     private static Map<String, Collection<ConfigAttribute>> resourceMap = null;  
  25.   
  26.     public MyInvocationSecurityMetadataSource() {  
  27.          loadResourceDefine();  
  28.      }  
  29.   
  30.     private void loadResourceDefine() {  
  31.          resourceMap = new HashMap<String, Collection<ConfigAttribute>>();  
  32.          Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();  
  33.          ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");  
  34.          atts.add(ca);  
  35.          resourceMap.put("/index.jsp", atts);  
  36.          resourceMap.put("/i.jsp", atts);  
  37.      }  
  38.   
  39.     // According to a URL, Find out permission configuration of this URL.  
  40.     public Collection<ConfigAttribute> getAttributes(Object object)  
  41.             throws IllegalArgumentException {  
  42.         // guess object is a URL.  
  43.          String url = ((FilterInvocation)object).getRequestUrl();  
  44.          Iterator<String> ite = resourceMap.keySet().iterator();  
  45.         while (ite.hasNext()) {  
  46.              String resURL = ite.next();  
  47.             if (urlMatcher.pathMatchesUrl(url, resURL)) {  
  48.                 return resourceMap.get(resURL);  
  49.              }  
  50.          }  
  51.         return null;  
  52.      }  
  53.   
  54.     public boolean supports(Class<?> clazz) {  
  55.         return true;  
  56.      }  
  57.       
  58.     public Collection<ConfigAttribute> getAllConfigAttributes() {  
  59.         return null;  
  60.      }  
  61.   
  62. }  

 

看看loadResourceDefine方法,我在这里,假定index.jspi.jsp这两个资源,需要ROLE_ADMIN角色的用户才能访问。

这个类中,还有一个最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。注意,我例子中使用的是AntUrlPathMatcher这个path matcher来检查URL是否与资源定义匹配,事实上你还要用正则的方式来匹配,或者自己实现一个matcher

 

6,剩下的就是最终的决策了,make a decision,其实也很容易,呵呵。

Java代码   收藏代码
  1. package com.example.spring.security;  
  2. import java.util.Collection;  
  3. import java.util.Iterator;  
  4.   
  5. import org.springframework.security.access.AccessDecisionManager;  
  6. import org.springframework.security.access.AccessDeniedException;  
  7. import org.springframework.security.access.ConfigAttribute;  
  8. import org.springframework.security.access.SecurityConfig;  
  9. import org.springframework.security.authentication.InsufficientAuthenticationException;  
  10. import org.springframework.security.core.Authentication;  
  11. import org.springframework.security.core.GrantedAuthority;  
  12.   
  13.   
  14. public class MyAccessDecisionManager implements AccessDecisionManager {  
  15.   
  16.     //In this method, need to compare authentication with configAttributes.  
  17.     // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.  
  18.     // 2, Check authentication has attribute in permission configuration (configAttributes)  
  19.     // 3, If not match corresponding authentication, throw a AccessDeniedException.  
  20.     public void decide(Authentication authentication, Object object,  
  21.              Collection<ConfigAttribute> configAttributes)  
  22.             throws AccessDeniedException, InsufficientAuthenticationException {  
  23.         if(configAttributes == null){  
  24.             return ;  
  25.          }  
  26.          System.out.println(object.toString());  //object is a URL.  
  27.          Iterator<ConfigAttribute> ite=configAttributes.iterator();  
  28.         while(ite.hasNext()){  
  29.              ConfigAttribute ca=ite.next();  
  30.              String needRole=((SecurityConfig)ca).getAttribute();  
  31.             for(GrantedAuthority ga:authentication.getAuthorities()){  
  32.                 if(needRole.equals(ga.getAuthority())){  //ga is user's role.  
  33.                     return;  
  34.                  }  
  35.              }  
  36.          }  
  37.         throw new AccessDeniedException("no right");  
  38.      }  
  39.   
  40.      @Override  
  41.     public boolean supports(ConfigAttribute attribute) {  
  42.         // TODO Auto-generated method stub  
  43.         return true;  
  44.      }  
  45.   
  46.      @Override  
  47.     public boolean supports(Class<?> clazz) {  
  48.         return true;  
  49.      }  
  50.   
  51.   
  52. }  
  在这个类中,最重要的是 decide 方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则 throw new AccessDeniedException("noright"); 这样,就会进入上面提到的 403.jsp 页面

4.Spring Security LDAP认证

10.1. 综述

LDAP通常被公司用作用户信息的中心资源库,同时也被当作一种认证服务。 它也可以为应用用户储存角色信息。

这里有很多如何对LDAP服务器进行配置的场景,所以Spring Security的LDAP提供器也是完全可配置的。 它使用为验证和角色检测提供了单独的策略接口,并提供了默认的实现,这些都是可配置成处理绝大多数情况。

你还是应该熟悉一下LDAP,在你在Spring Security使用它之前。 下面的链接提供了很好的概念介绍,也是一个使用免费的LDAP服务器建立一个目录http://www.zytrax.com/books/ldap/的指南。 我们也应该熟悉一下通过JNDI API使用java访问LDAP。 我们没有在LDAP提供器里使用任何第三方LDAP库(Mozilla, JLDAP等等),但是还是用到了Spring LDAP,所以如果你希望自己进行自定义,对这个工程熟悉一下也是有好处的。

10.2. 在Spring Security里使用LDAP

Spring Security的LDAP认证可以粗略分成以下几部分。

  1. 从登录名中获得唯一的“辨别名称”或DN。 这就意味着要对目录执行搜索,除非预先知道了用户名和DN之前的明确映射关系。

  2. 验证这个用户,进行绑定用户,或调用远程“比较”操作,比对用户的密码和DN在目录入口中的密码属性。

  3. 为这个用户读取权限队列。

例外情况是,当LDAP目录只是用来检索用户信息和进行本地验证的时候,这也许不可能的,因为目录的属性,比如对用户密码属性,常常被设置成只读权限。

让我们看看下面的一些配置场景。 要是想得到所有可用的配置选项,请参考安全命名空间结构(使用你的XML编辑器应该就可以看到所有有效信息)。

10.3. 配置LDAP服务器

你需要做的第一件事是配置服务器,它里面应该存放着认证信息。 这可以使用安全命名空间里的<ldap-server>元素实现。 使用url属性指向一个外部LDAP服务器:

    <ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
                
            

 

10.3.1. 使用嵌入测试服务器

这个<ldap-server>元素也可以用来创建一个嵌入服务器,这在测试和演示的时候特别有用。 在这种情况,你不需要使用url属性:

    <ldap-server root="dc=springframework,dc=org"/>
        
    

这里我们指定目录的根DIT应该是“dc=springframework,dc=org”,这是默认的。 使用这种方式,命名空间解析器会建立一个嵌入Apache目录服务器,然后检索classpath下的LDIF文件,尝试从它里边把数据加载到服务器里。 你可以通过ldif属性自定义这些行为,这样可以定义具体要加载哪个LDIF资源:

    <ldap-server ldif="classpath:users.ldif" />
        

这就让启动和运行LDAP变得更轻松了,因为使用一个外部服务器还是不大方便。 它也避免链接到Apache目录服务器的复杂bean配置。 如果使用普通Spring bean配置方法会变的更加混乱。 你必须把必要的Apache目录依赖的jar放到你的程序中。 这些都可以从LDAP示例程序中获得。

10.3.2. 使用绑定认证

这是一个非常常见的LDAP认证场景。

    <ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
                     

这个很简单的例子可以根据用户登录名提供的模式为用户获得DN,然后尝试和用户的登录密码进行绑定。 如果所有用户都保存到一个目录的单独节点下就没有问题。 如果你想配置一个LDAP搜索过滤器来定位用户,你可以使用如下配置:

    <ldap-authentication-provider user-search-filter="(uid={0})" user-search-base="ou=people"/>
                    

如果使用了上面的服务器定义,它会在DNou=people,dc=springframework,dc=org下执行搜索,使用user-search-filter里的值作为过滤条件。 然后把用户登录名作为过滤名称的一个参数。 如果没有提供user-search-base,搜索将从根开始。

10.3.3. 读取授权

如果从LDAP目录的组里读取权限信息呢,这是通过下面的属性控制的。

  • group-search-base。定义目录树部分,哪个组应该执行搜索。

  • group-role-attribute。这个属性包含了组入口中定义的权限名称。默认是 cn

  • group-search-filter。这个过滤器用来搜索组的关系。 默认是uniqueMember={0},对应于groupOfUniqueMembersLDAP类。 在这情况下,取代参数是用户的辨别名称。 如果你想对登录名搜索,可以使用{1}这个参数。

因此,如果我们使用下面进行配置

    <ldap-authentication-provider user-dn-pattern="uid={0},ou=people" group-search-base="ou=groups" />
    

并以用户“ben”的身份通过认证,在读取权限信息的子流程里,要在目录入口ou=groups,dc=springframework,dc=org下执行搜索,查找包含uniqueMember属性值为ou=groups,dc=springframework,dc=org的入口。 默认,权限名都要以ROLE_作为前缀。 你可以使用role-prefix属性修改它。 如果你不想使用任何前缀,可以使用role-prefix="none"。 要想得到更多读取权限的信息,可以查看DefaultLdapAuthoritiesPopulator类的Javadoc。

10.4. 实现类

我们上面使用到的命名空间选项很容易使用,也比使用spring bean更准确。 也有可能你需要知道如何配置在你的application context里配置Spring Security LDAP目录。 比如,你可能想自定义一些类的行为。 如果你想使用命名空间配置,你可以跳过这节,直接进入下一段。

最主要的LDAP提供器类是org.springframework.security.providers.ldap.LdapAuthenticationProvider。 这个bean自己没做什么事情,而是代理了其他两个bean的工作,一个是LdapAuthenticator,一个是LdapAuthoritiesPopulator,用来处理用户认证和检索用户的GrantedAuthority属性集合。

10.4.1. LdapAuthenticator实现

验证者还负责检索所有需要的用户属性。 这是因为对于属性的授权可能依赖于使用的验证类型 比如,如果对某个用户进行绑定,它也许必须通过用户自己的授权才能进行读取。

当前Spring Security提供两种验证策略:

  • 直接去LDAP服务器验证(“绑定”验证)。

  • 比较密码,将用户提供的密码与资源库中保存的进行比较。 这可以通过检索密码属性的值并在本地检测,或者执行LDAP“比较”操作,提供用来比较的密码是从服务器获得的,绝对不会检索真实密码的值。

 

10.4.1.1. 常用功能

在认证一个用户之前(使用任何一个策略),辨别名称(DN)必须从系统提供的登录名中获得。 这可以通过,简单的模式匹配(设置setUserDnPatterns数组属性)或者设置userSearch属性。 为了实现DN模式匹配方法,一个标准的java模式格式被用到了,登录名将被参数{0}替代。 这个模式应该和DN有关系,并绑定到配置好的SpringSecurityContextSource(看看链接到LDAP服务器那节,获得更多信息)。 比如,如果你使用了LDAP服务的URL是ldap://monkeymachine.co.uk/dc=springframework,dc=org,并有一个模式uid={0},ou=greatapes,然后登录名"gorilla"会映射到DNuid=gorilla,ou=greatapes,dc=springframework,dc=org。 每个配置好的DN模式将尝试进行定位,直到有一个匹配上。 使用搜索获得信息,看看下面的安全对象那节。 两种方式也可以结合在一起使用 - 模式会先被检测一下,然后如果没有找到匹配的DN,就会使用搜索。

10.4.1.2. BindAuthenticator

这个类 org.springframework.security.providers.ldap.authenticator.BindAuthenticator 实现了绑定认证策略。 它只是尝试对用户进行绑定。

10.4.1.3. PasswordComparisonAuthenticator

这个类 org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator 实现了密码比较认证策略。

10.4.1.4. 活动目录认证

除了标准LDAP认证以外(绑定到一个DN),活动目录对于用户认证提供了自己的非标准语法。

10.4.2. 链接到LDAP服务器

上面讨论的bean必须连接到服务器。 它们都必须使用SpringSecurityContextSource,这个是Spring LDAP的一个扩展。 除非你有特定的需求,你通常只需要配置一个DefaultSpringSecurityContextSource bean,这个可以使用你的LDAP服务器的URL进行配置,可选项还有管理员用户的用户名和密码,这将默认用在绑定服务器的时候(而不是匿名绑定)。 参考Spring LDAP的AbstractContextSource类的Javadoc获得更多信息。

10.4.3. LDAP搜索对象

通常,比简单DN匹配越来越复杂的策略需要在目录里定位一个用户入口。 这可以使用LdapUserSearch的一个示例,它可以提供认证者实现,比如让他们定位一个用户。 提供的实现是FilterBasedLdapUserSearch

10.4.3.1. FilterBasedLdapUserSearch

这个bean使用一个LDAP过滤器,来匹配目录里的用户对象。 这个过程在javadoc里进行过解释,在对应的搜索方法,JDK DirContext class。 就如那里解释的,搜索过滤条件可以通过方法指定。 对于这个类,唯一合法的参数是{0},它会代替用户的登录名。

10.4.4. LdapAuthoritiesPopulator

在成功认证用户之后,LdapAuthenticationProvider会调用配置好的LdapAuthoritiesPopulator bean,尝试读取用户的授权集合。 这个DefaultLdapAuthoritiesPopulator是一个实现类,它将通过搜索目录读取授权,查找用户成员所在的组(典型的这会是目录中的groupOfNamesgroupOfUniqueNames入口)。 查看这个类的Javadoc获得它如何工作的更多信息。

10.4.5. Spring Bean配置

典型的配置方法,使用到像我们这在里讨论的这些bean,就像这样:

<bean id="contextSource"
        class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
  <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
  <property name="password" value="password"/>
</bean>

<bean id="ldapAuthProvider"
        class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
  <constructor-arg>
    <bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
      <constructor-arg ref="contextSource"/>
      <property name="userDnPatterns">
        <list><value>uid={0},ou=people</value></list>
      </property>
    </bean>
  </constructor-arg>
  <constructor-arg>
    <bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
      <constructor-arg ref="contextSource"/>
      <constructor-arg value="ou=groups"/>
      <property name="groupRoleAttribute" value="ou"/>
    </bean>
  </constructor-arg>
</bean>
                

这里建立了一个提供器,访问LDAP服务,URL是 ldap://monkeymachine:389/dc=springframework,dc=org。 认证会被执行,尝试绑定这个DNuid=<user-login-name>,ou=people,dc=springframework,dc=org。 在成功认证之后,会通过查找下面的DNou=groups,dc=springframework,dc=org 使用默认的过滤条件 (member=<user's-DN>),将角色分配给用户。 角色名会通过每个匹配的“ou”属性获得。

要配置用户的搜索对象,使用过滤条件 (uid=<user-login-name>) 替代DN匹配(或附加到它上面),你需要配置下面的bean

<bean id="userSearch"
    class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
  <constructor-arg index="0" value=""/>
  <constructor-arg index="1" value="(uid={0})"/>
  <constructor-arg index="2" ref="contextSource" />
</bean> 
                

并使用它,设置认证者的userSearch属性。 这个认证者会调用搜索对象,在尝试绑定到用户之前获得正确的用户DN。

10.4.6. LDAP属性和自定义UserDetails

使用LdapAuthenticationProvider进行认证的结果,和使用普通Spring Security认证一样,都要使用标准UserDetailsService接口。 它会创建一个UserDetails对象,并保存到返回的Authentication对象里。 在使用UserDetailsService时,常见的需求是可以自定义这个实现,添加额外的属性。 在使用LDAP的时候,这些基本都来自用户入口的属性。UserDetails对象的创建结果被提供者的UserDetailsContextMapper策略控制,它负责在用户对象和LDAP环境数据之间进行映射:

public interface UserDetailsContextMapper {
    UserDetails mapUserFromContext(DirContextOperations ctx, String username, GrantedAuthority[] authority);

    void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}
                

只有第一个方法与认证有关。 如果你提供这个接口的实现,你可以精确控制如何创建UserDetails对象。 第一个参数是Spring LDAP的DirContextOperations实例,他给你访问加载的LDAP属性的通道。username参数是用来认证的名字,最后一个参数是从用户加载的授权列表。]

环境数据加载的方式不同,视乎你采用的认证方法。 使用BindAuthenticatior,从绑定操作返回的环境会用来读取属性,否则数据会通过标准的环境,从配置好的ContextSource获得(当测试配置好定位用户,这会从搜索对象中获得数据)。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值