Przeglądaj źródła

SEC-746: impossible to specify errorPage for the AccessDeniedHandlerImp when using namespace based configuration
http://jira.springframework.org/browse/SEC-746. Added access-denied-page to http element.

Luke Taylor 17 lat temu
rodzic
commit
243b5f4a2a

+ 27 - 2
core/src/main/java/org/springframework/security/util/FieldUtils.java

@@ -17,6 +17,7 @@ package org.springframework.security.util;
 
 import org.springframework.util.Assert;
 import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
 
 import java.lang.reflect.Field;
 
@@ -56,8 +57,7 @@ public final class FieldUtils {
      *
      * @throws IllegalStateException if field could not be found
      */
-    public static Field getField(Class clazz, String fieldName)
-        throws IllegalStateException {
+    public static Field getField(Class clazz, String fieldName) throws IllegalStateException {
         Assert.notNull(clazz, "Class required");
         Assert.hasText(fieldName, "Field name required");
 
@@ -72,6 +72,31 @@ public final class FieldUtils {
             throw new IllegalStateException("Could not locate field '" + fieldName + "' on class " + clazz);
         }
     }
+    
+    /**
+     * Returns the value of a (nested) field on a bean. Intended for testing.
+     * @param bean the object
+     * @param fieldName the field name, with "." separating nested properties
+     * @return the value of the nested field
+     */
+    public static Object getFieldValue(Object bean, String fieldName) throws IllegalAccessException {
+        Assert.notNull(bean, "Bean cannot be null");        
+        Assert.hasText(fieldName, "Field name required");        
+        String[] nestedFields = StringUtils.tokenizeToStringArray(fieldName, ".");
+        Class componentClass = bean.getClass();
+        Field field = null;
+        Object value = bean;
+        
+        for (int i=0; i < nestedFields.length; i++) {
+            field = getField(componentClass, nestedFields[i]);
+            field.setAccessible(true);
+            value = field.get(value);            
+            componentClass = value.getClass();
+        }
+        
+        return value;
+        
+    }
 
     public static String getMutatorName(String fieldName) {
         Assert.hasText(fieldName, "FieldName required");

+ 14 - 13
core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc

@@ -1,12 +1,9 @@
-
-namespace beans = "http://www.springframework.org/schema/beans"
 namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
-namespace security = "http://www.springframework.org/schema/security"
 datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
 
 default namespace = "http://www.springframework.org/schema/security"
 
-start = http | ldap-server | authentication-provider | ldap-authentication-provider | user-service
+start = http | ldap-server | authentication-provider | ldap-authentication-provider | any-user-service | ldap-server | ldap-authentication-provider
 
 hash =
     ## Defines the hashing algorithm used on user passwords. We recommend strongly against using MD4, as it is a very weak hashing algorithm.
@@ -52,6 +49,8 @@ user-property =
 system-wide =
     ## A single value that will be used as the salt for a password encoder. 
     attribute system-wide {xsd:string}
+    
+boolean = "true" | "false"
 
 
 ldap-server =
@@ -189,7 +188,7 @@ http =
    element http {http.attlist, (intercept-url+ & form-login? & openid-login & x509? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & port-mappings) }
 http.attlist &=
     ## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".
-    attribute auto-config {"true" | "false" }?
+    attribute auto-config {boolean}?
 http.attlist &=
     ## Controls the eagerness with which an HTTP session is created. If not set, defaults to "ifRequired".
     attribute create-session {"ifRequired" | "always" | "never" }?
@@ -198,10 +197,10 @@ http.attlist &=
     path-type?
 http.attlist &=
     ## Whether test URLs should be converted to lower case prior to comparing with defined path patterns. If unspecified, defaults to "true".
-    attribute lowercase-comparisons {"true" | "false"}?
+    attribute lowercase-comparisons {boolean}?
 http.attlist &=
     ## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
-    attribute servlet-api-provision {"true" | "false"}?
+    attribute servlet-api-provision {boolean}?
 http.attlist &=
     ## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
     attribute access-decision-manager-ref {xsd:string}?
@@ -216,8 +215,10 @@ http.attlist &=
     attribute entry-point-ref {xsd:string}?
 http.attlist &=
     ## Corresponds to the observeOncePerRequest property of FilterSecurityInterceptor. Defaults to "false"
-    attribute once-per-request {"true" | "false"}?
-
+    attribute once-per-request {boolean}?
+http.attlist &=
+    ## Allows the access denied page to be set (the user will be redirected here if an AccessDeniedException is raised).
+    attribute access-denied-page {xsd:string}?
 
 intercept-url =
     ## Specifies the access attributes and/or filter list for a particular set of URLs.
@@ -250,7 +251,7 @@ logout.attlist &=
     attribute logout-success-url {xsd:string}?
 logout.attlist &=
 	## Specifies whether a logout also causes HttpSession invalidation, which is generally desirable. If unspecified, defaults to true.
-    attribute invalidate-session {"true" | "false"}?
+    attribute invalidate-session {boolean}?
 
 form-login =
     ## Sets up a form login configuration for authentication with a username and password
@@ -294,7 +295,7 @@ fids.attlist &=
     id?
 fids.attlist &=
     ## as for http element
-    attribute lowercase-comparisons {"true" | "false"}?
+    attribute lowercase-comparisons {boolean}?
 fids.attlist &=
     ## as for http element
     path-type?
@@ -312,7 +313,7 @@ concurrent-sessions.attlist &=
 concurrent-sessions.attlist &=
     attribute expired-url {xsd:string}?
 concurrent-sessions.attlist &=
-    attribute exception-if-maximum-exceeded {"true" | "false"}?
+    attribute exception-if-maximum-exceeded {boolean}?
 concurrent-sessions.attlist &=
     ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration
     attribute session-registry-alias {xsd:string}?
@@ -400,7 +401,7 @@ user.attlist &=
     attribute authorities {xsd:string}
 user.attlist &=
 	  ## Can be set to "true" to mark an account as locked and unusable.
-    attribute locked {"true" | "false"}?
+    attribute locked {boolean}?
 
 
 jdbc-user-service =

+ 20 - 57
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

@@ -144,6 +144,12 @@
       </xs:annotation>
     </xs:attribute>
   </xs:attributeGroup>
+  <xs:simpleType name="boolean">
+    <xs:restriction base="xs:token">
+      <xs:enumeration value="true"/>
+      <xs:enumeration value="false"/>
+    </xs:restriction>
+  </xs:simpleType>
   <xs:element name="ldap-server">
     <xs:annotation>
       <xs:documentation>Defines an LDAP server location or starts an embedded server. The url
@@ -609,7 +615,7 @@
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="http.attlist">
-    <xs:attribute name="auto-config">
+    <xs:attribute name="auto-config" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Automatically registers a login form, BASIC authentication, anonymous
           authentication, logout services, remember-me and servlet-api-integration. If set to
@@ -617,12 +623,6 @@
           configuration of each by providing the respective element). If unspecified, defaults to
           "false".</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
     <xs:attribute name="create-session">
       <xs:annotation>
@@ -650,30 +650,18 @@
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>
-    <xs:attribute name="lowercase-comparisons">
+    <xs:attribute name="lowercase-comparisons" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Whether test URLs should be converted to lower case prior to comparing
           with defined path patterns. If unspecified, defaults to "true".</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
-    <xs:attribute name="servlet-api-provision">
+    <xs:attribute name="servlet-api-provision" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Provides versions of HttpServletRequest security methods such as
           isUserInRole() and getPrincipal() which are implemented by accessing the Spring
           SecurityContext. Defaults to "true".</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
     <xs:attribute name="access-decision-manager-ref" type="xs:string">
       <xs:annotation>
@@ -710,17 +698,17 @@
         used.</xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="once-per-request">
+    <xs:attribute name="once-per-request" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Corresponds to the observeOncePerRequest property of
           FilterSecurityInterceptor. Defaults to "false"</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
+    </xs:attribute>
+    <xs:attribute name="access-denied-page" type="xs:string">
+      <xs:annotation>
+        <xs:documentation>Allows the access denied page to be set (the user will be redirected here
+          if an AccessDeniedException is raised).</xs:documentation>
+      </xs:annotation>
     </xs:attribute>
   </xs:attributeGroup>
   <xs:attributeGroup name="intercept-url.attlist">
@@ -794,17 +782,11 @@
           specified, defaults to /.</xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="invalidate-session">
+    <xs:attribute name="invalidate-session" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Specifies whether a logout also causes HttpSession invalidation, which is
           generally desirable. If unspecified, defaults to true.</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
   </xs:attributeGroup>
   <xs:attributeGroup name="form-login.attlist">
@@ -914,16 +896,10 @@
           context.</xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="lowercase-comparisons">
+    <xs:attribute name="lowercase-comparisons" type="security:boolean">
       <xs:annotation>
         <xs:documentation>as for http element</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
     <xs:attribute name="path-type">
       <xs:annotation>
@@ -942,14 +918,7 @@
   <xs:attributeGroup name="concurrent-sessions.attlist">
     <xs:attribute name="max-sessions" type="xs:positiveInteger"/>
     <xs:attribute name="expired-url" type="xs:string"/>
-    <xs:attribute name="exception-if-maximum-exceeded">
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
-    </xs:attribute>
+    <xs:attribute name="exception-if-maximum-exceeded" type="security:boolean"/>
     <xs:attribute name="session-registry-alias" type="xs:string">
       <xs:annotation>
         <xs:documentation>Allows you to define an alias for the SessionRegistry bean in order to
@@ -1136,17 +1105,11 @@
           comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"</xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="locked">
+    <xs:attribute name="locked" type="security:boolean">
       <xs:annotation>
         <xs:documentation>Can be set to "true" to mark an account as locked and
         unusable.</xs:documentation>
       </xs:annotation>
-      <xs:simpleType>
-        <xs:restriction base="xs:token">
-          <xs:enumeration value="true"/>
-          <xs:enumeration value="false"/>
-        </xs:restriction>
-      </xs:simpleType>
     </xs:attribute>
   </xs:attributeGroup>
   <xs:element name="jdbc-user-service" substitutionGroup="security:any-user-service">

+ 13 - 7
core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java

@@ -1,10 +1,6 @@
 package org.springframework.security.config;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 import java.util.Iterator;
 import java.util.List;
@@ -35,11 +31,11 @@ import org.springframework.security.ui.WebAuthenticationDetails;
 import org.springframework.security.ui.basicauth.BasicProcessingFilter;
 import org.springframework.security.ui.logout.LogoutFilter;
 import org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter;
-import org.springframework.security.ui.rememberme.AbstractRememberMeServices;
 import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices;
 import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
 import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
 import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
+import org.springframework.security.util.FieldUtils;
 import org.springframework.security.util.FilterChainProxy;
 import org.springframework.security.util.InMemoryXmlApplicationContext;
 import org.springframework.security.util.PortMapperImpl;
@@ -198,6 +194,17 @@ public class HttpSecurityBeanDefinitionParserTests {
         FilterSecurityInterceptor fsi = (FilterSecurityInterceptor) filters.get(filters.size() - 1);
         
         assertTrue(fsi.isObserveOncePerRequest());
+    }
+    
+    @Test
+    public void accessDeniedPageAttributeIsSupported() throws Exception {
+        setContext("<http access-denied-page='/access-denied'><http-basic /></http>" + AUTH_PROVIDER_XML);
+        FilterChainProxy filterChainProxy = getFilterChainProxy();
+        List filters = filterChainProxy.getFilters("/someurl");
+        
+        ExceptionTranslationFilter etf = (ExceptionTranslationFilter) filters.get(filters.size() - 2);
+        
+        assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
     }    
     
     @Test
@@ -376,7 +383,6 @@ public class HttpSecurityBeanDefinitionParserTests {
         assertEquals("Hello from the post processor!", service.getPostProcessorWasHere());
     }
     
-    
     private void setContext(String context) {
         appContext = new InMemoryXmlApplicationContext(context);
     }