paulwong

          Spring Security 3.x 完整入門教程(轉(zhuǎn))

          Spring Security 3.x 出來一段時間了,跟Acegi是大不同了,與2.x的版本也有一些小小的區(qū)別,網(wǎng)上有一些文檔,也有人翻譯Spring Security 3.x的guide,但通過閱讀guide,無法馬上就能很容易的實現(xiàn)一個完整的實例。

          我花了點兒時間,根據(jù)以前的實戰(zhàn)經(jīng)驗,整理了一份完整的入門教程,供需要的朋友們參考。
          1,建一個web project,并導(dǎo)入所有需要的lib,這步就不多講了。
          2,配置web.xml,使用Spring的機制裝載:


          <?xml version="1.0" encoding="UTF-8"?>
          <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
              xmlns:xsi
          ="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation
          ="http://java.sun.com/xml/ns/j2ee 
              http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
          >
              
          <context-param>
                  
          <param-name>contextConfigLocation</param-name>
                  
          <param-value>classpath:applicationContext*.xml</param-value>
              
          </context-param>

              
          <listener>
                  
          <listener-class>
                      org.springframework.web.context.ContextLoaderListener
                  
          </listener-class>
              
          </listener>

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


              
          <welcome-file-list>
                  
          <welcome-file>login.jsp</welcome-file>
              
          </welcome-file-list>
          </web-app>
          這個文件中的內(nèi)容我相信大家都很熟悉了,不再多說了。

          2,來看看applicationContext-security.xml這個配置文件,關(guān)于Spring Security的配置均在其中:


          <?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-3.0.xsd
                     http://www.springframework.org/schema/security
                     http://www.springframework.org/schema/security/spring-security-3.0.xsd"
          >

              
          <http access-denied-page="/403.jsp"><!-- 當(dāng)訪問被拒絕時,會轉(zhuǎn)到403.jsp -->
                  
          <intercept-url pattern="/login.jsp" filters="none" />
                  
          <form-login login-page="/login.jsp"
                      authentication-failure-url
          ="/login.jsp?error=true"
                      default-target-url
          ="/index.jsp" />
                  
          <logout logout-success-url="/login.jsp" />
                  
          <http-basic />
                  
          <!-- 增加一個filter,這點與Acegi是不一樣的,不能修改默認(rèn)的filter了,這個filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
                  
          <custom-filter before="FILTER_SECURITY_INTERCEPTOR"
                      ref
          ="myFilter" />
              
          </http>

              
          <!-- 一個自定義的filter,必須包含authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,
              我們的所有控制將在這三個類中實現(xiàn),解釋詳見具體配置 
          -->
              
          <beans:bean id="myFilter" class="com.robin.erp.fwk.security.MyFilterSecurityInterceptor">
                  
          <beans:property name="authenticationManager"
                      ref
          ="authenticationManager" />
                  
          <beans:property name="accessDecisionManager"
                      ref
          ="myAccessDecisionManagerBean" />
                  
          <beans:property name="securityMetadataSource"
                      ref
          ="securityMetadataSource" />
              
          </beans:bean>
              
              
          <!-- 認(rèn)證管理器,實現(xiàn)用戶認(rèn)證的入口,主要實現(xiàn)UserDetailsService接口即可 -->
              
          <authentication-manager alias="authenticationManager">
                  
          <authentication-provider
                      
          user-service-ref="myUserDetailService">
                      
          <!--   如果用戶的密碼采用加密的話,可以加點“鹽”
                          <password-encoder hash="md5" />
                      
          -->
                  
          </authentication-provider>
              
          </authentication-manager>
              
          <beans:bean id="myUserDetailService"
                  class
          ="com.robin.erp.fwk.security.MyUserDetailService" />

              
          <!-- 訪問決策器,決定某個用戶具有的角色,是否有足夠的權(quán)限去訪問某個資源 -->
              
          <beans:bean id="myAccessDecisionManagerBean"
                  class
          ="com.robin.erp.fwk.security.MyAccessDecisionManager">
              
          </beans:bean>
              
              
          <!-- 資源源數(shù)據(jù)定義,即定義某一資源可以被哪些角色訪問 -->
              
          <beans:bean id="securityMetadataSource"
                  class
          ="com.robin.erp.fwk.security.MyInvocationSecurityMetadataSource" />

          </beans:beans>


          3,來看看自定義filter的實現(xiàn):


          package com.robin.erp.fwk.security;
          import java.io.IOException;

          import javax.servlet.Filter;
          import javax.servlet.FilterChain;
          import javax.servlet.FilterConfig;
          import javax.servlet.ServletException;
          import javax.servlet.ServletRequest;
          import javax.servlet.ServletResponse;

          import org.springframework.security.access.SecurityMetadataSource;
          import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
          import org.springframework.security.access.intercept.InterceptorStatusToken;
          import org.springframework.security.web.FilterInvocation;
          import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

          public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
                  
          implements Filter {

              
          private FilterInvocationSecurityMetadataSource securityMetadataSource;

              
          // ~ Methods
              
          // ========================================================================================================

              
          /** *//**
               * Method that is actually called by the filter chain. Simply delegates to
               * the {
          @link #invoke(FilterInvocation)} method.
               * 
               * 
          @param request
               *            the servlet request
               * 
          @param response
               *            the servlet response
               * 
          @param chain
               *            the filter chain
               * 
               * 
          @throws IOException
               *             if the filter chain fails
               * 
          @throws ServletException
               *             if the filter chain fails
               
          */
              
          public void doFilter(ServletRequest request, ServletResponse response,
                      FilterChain chain) 
          throws IOException, ServletException {
                  FilterInvocation fi 
          = new FilterInvocation(request, response, chain);
                  invoke(fi);
              }

              
          public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
                  
          return this.securityMetadataSource;
              }

              
          public Class<? extends Object> getSecureObjectClass() {
                  
          return FilterInvocation.class;
              }

              
          public void invoke(FilterInvocation fi) throws IOException,
                      ServletException {
                  InterceptorStatusToken token 
          = super.beforeInvocation(fi);
                  
          try {
                      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                  } 
          finally {
                      
          super.afterInvocation(token, null);
                  }
              }

              
          public SecurityMetadataSource obtainSecurityMetadataSource() {
                  
          return this.securityMetadataSource;
              }

              
          public void setSecurityMetadataSource(
                      FilterInvocationSecurityMetadataSource newSource) {
                  
          this.securityMetadataSource = newSource;
              }

              @Override
              
          public void destroy() {
              }

              @Override
              
          public void init(FilterConfig arg0) throws ServletException {
              }

          }
          最核心的代碼就是invoke方法中的InterceptorStatusToken token = super.beforeInvocation(fi);這一句,即在執(zhí)行doFilter之前,進(jìn)行權(quán)限的檢查,而具體的實現(xiàn)已經(jīng)交給accessDecisionManager了,下文中會講述。

          4,來看看authentication-provider的實現(xiàn):


          package com.robin.erp.fwk.security;
          import java.util.ArrayList;
          import java.util.Collection;

          import org.springframework.dao.DataAccessException;
          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.GrantedAuthorityImpl;
          import org.springframework.security.core.userdetails.User;
          import org.springframework.security.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.core.userdetails.UsernameNotFoundException;

          public class MyUserDetailService implements UserDetailsService {

              @Override
              
          public UserDetails loadUserByUsername(String username)
                      
          throws UsernameNotFoundException, DataAccessException {
                  Collection
          <GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
                  GrantedAuthorityImpl auth2
          =new GrantedAuthorityImpl("ROLE_ADMIN");
                  auths.add(auth2);
                  
          if(username.equals("robin1")){
                      auths
          =new ArrayList<GrantedAuthority>();
                      GrantedAuthorityImpl auth1
          =new GrantedAuthorityImpl("ROLE_ROBIN");
                      auths.add(auth1);
                  }

                  
          //        User(String username, String password, boolean enabled, boolean accountNonExpired,
          //                    boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities) {
                  User user = new User(username,
                          
          "robin"truetruetruetrue, auths);
                  
          return user;
              }

              
          }

          在這個類中,你就可以從數(shù)據(jù)庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等,我想這么簡單的代碼就不再多解釋了。

          5,對于資源的訪問權(quán)限的定義,我們通過實現(xiàn)FilterInvocationSecurityMetadataSource這個接口來初始化數(shù)據(jù)。

          package com.robin.erp.fwk.security;
          import java.util.ArrayList;
          import java.util.Collection;
          import java.util.HashMap;
          import java.util.Iterator;
          import java.util.Map;

          import org.springframework.security.access.ConfigAttribute;
          import org.springframework.security.access.SecurityConfig;
          import org.springframework.security.web.FilterInvocation;
          import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
          import org.springframework.security.web.util.AntUrlPathMatcher;
          import org.springframework.security.web.util.UrlMatcher;

          /** *//**
           * 
           * 此類在初始化時,應(yīng)該取到所有資源及其對應(yīng)角色的定義
           * 
           * 
          @author Robin
           * 
           
          */
          public class MyInvocationSecurityMetadataSource
                  
          implements FilterInvocationSecurityMetadataSource {
              
          private UrlMatcher urlMatcher = new AntUrlPathMatcher();;
              
          private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

              
          public MyInvocationSecurityMetadataSource() {
                  loadResourceDefine();
              }

              
          private void loadResourceDefine() {
                  resourceMap 
          = new HashMap<String, Collection<ConfigAttribute>>();
                  Collection
          <ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                  ConfigAttribute ca 
          = new SecurityConfig("ROLE_ADMIN");
                  atts.add(ca);
                  resourceMap.put(
          "/index.jsp", atts);
                  resourceMap.put(
          "/i.jap", atts);
              }

              
          // According to a URL, Find out permission configuration of this URL.
              public Collection<ConfigAttribute> getAttributes(Object object)
                      
          throws IllegalArgumentException {
                  
          // guess object is a URL.
                  String url = ((FilterInvocation)object).getRequestUrl();
                  Iterator
          <String> ite = resourceMap.keySet().iterator();
                  
          while (ite.hasNext()) {
                      String resURL 
          = ite.next();
                      
          if (urlMatcher.pathMatchesUrl(url, resURL)) {
                          
          return resourceMap.get(resURL);
                      }
                  }
                  
          return null;
              }

              
          public boolean supports(Class<?> clazz) {
                  
          return true;
              }
              
              
          public Collection<ConfigAttribute> getAllConfigAttributes() {
                  
          return null;
              }

          }
          看看loadResourceDefine方法,我在這里,假定index.jsp和i.jsp這兩個資源,需要ROLE_ADMIN角色的用戶才能訪問。
          這個類中,還有一個最核心的地方,就是提供某個資源對應(yīng)的權(quán)限定義,即getAttributes方法返回的結(jié)果。注意,我例子中使用的是AntUrlPathMatcher這個path matcher來檢查URL是否與資源定義匹配,事實上你還要用正則的方式來匹配,或者自己實現(xiàn)一個matcher。

          6,剩下的就是最終的決策了,make a decision,其實也很容易,呵呵。

          package com.robin.erp.fwk.security;
          import java.util.Collection;
          import java.util.Iterator;

          import org.springframework.security.access.AccessDecisionManager;
          import org.springframework.security.access.AccessDeniedException;
          import org.springframework.security.access.ConfigAttribute;
          import org.springframework.security.access.SecurityConfig;
          import org.springframework.security.authentication.InsufficientAuthenticationException;
          import org.springframework.security.core.Authentication;
          import org.springframework.security.core.GrantedAuthority;


          public class MyAccessDecisionManager implements AccessDecisionManager {

              
          //In this method, need to compare authentication with configAttributes.
              
          // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.
              
          // 2, Check authentication has attribute in permission configuration (configAttributes)
              
          // 3, If not match corresponding authentication, throw a AccessDeniedException.
              public void decide(Authentication authentication, Object object,
                      Collection
          <ConfigAttribute> configAttributes)
                      
          throws AccessDeniedException, InsufficientAuthenticationException {
                  
          if(configAttributes == null){
                      
          return ;
                  }

                  System.out.println(object.toString());  
          //object is a URL.
                  Iterator<ConfigAttribute> ite=configAttributes.iterator();
                  
          while(ite.hasNext()){
                      ConfigAttribute ca
          =ite.next();
                      String needRole
          =((SecurityConfig)ca).getAttribute();
                      
          for(GrantedAuthority ga:authentication.getAuthorities()){
                          
          if(needRole.equals(ga.getAuthority())){  //ga is user's role.
                              return;
                          }

                      }

                  }

                  
          throw new AccessDeniedException("no right");
              }


              @Override
              
          public boolean supports(ConfigAttribute attribute) {
                  
          // TODO Auto-generated method stub
                  return true;
              }


              @Override
              
          public boolean supports(Class<?> clazz) {
                  
          return true;
              }



          }



          在這個類中,最重要的是decide方法,如果不存在對該資源的定義,直接放行;否則,如果找到正確的角色,即認(rèn)為擁有權(quán)限,并放行,否則throw new AccessDeniedException("no right");這樣,就會進(jìn)入上面提到的403.jsp頁面。

          posted on 2010-03-10 21:56 paulwong 閱讀(4464) 評論(1)  編輯  收藏 所屬分類: SPRING

          Feedback

          # re: Spring Security 3.x 完整入門教程(轉(zhuǎn)) 2016-04-21 18:54 qwe

          eqweweqw  回復(fù)  更多評論   


          主站蜘蛛池模板: 丽江市| 秦皇岛市| 长白| 丰原市| 闻喜县| 皋兰县| 东乌珠穆沁旗| 类乌齐县| 罗城| 安顺市| 慈利县| 桃园县| 赣榆县| 武平县| 海安县| 徐闻县| 布拖县| 定州市| 城固县| 韩城市| 宜良县| 西畴县| 永平县| 泗水县| 合肥市| 武隆县| 板桥市| 奉新县| 桑植县| 望江县| 西城区| 西安市| 辽阳县| 霍邱县| 新乐市| 五莲县| 兴宁市| 小金县| 栾城县| 东阳市| 常州市|