2012-08-03 94 views
2

我已经使用Spring安全保护我的服务器并尝试测试它。 我的测试包括登录测试用户(已存在于数据库中),然后尝试访问需要用户进行身份验证并具有特定角色的安全URL。测试Spring Security

这里是我的代码,

我创建了一个安全上下文:

<!-- URL's that start with the "app/secured" prefix requires authentication --> 
<http auto-config="false" use-expressions="true"> 
    <form-login login-processing-url="/app/login" 
     authentication-success-handler-ref="ajaxAuthenticationSuccessHandler" 
     authentication-failure-handler-ref="ajaxAuthenticationFailureHandler" /> 
    <intercept-url pattern="/**" access="permitAll" /> 
    <intercept-url pattern="**/app/secured/**" access="isAuthenticated()" /> 
</http> 

<authentication-manager alias="authenticationManager"> 
    <authentication-provider user-service-ref="myUserDetailsService"> 
     <password-encoder ref="passwordEncoder"> 
      <salt-source user-property="creationTime" /> 
     </password-encoder> 
    </authentication-provider> 
</authentication-manager> 

<beans:bean id="passwordEncoder" 
    class="me.co.server.bl.security.ExtendedShaPasswordEncoder" /> 

<beans:bean id="ajaxAuthenticationSuccessHandler" 
    class="me.co.server.web.resource.register.login.AjaxAuthenticationSuccessHandler" /> 

<beans:bean id="ajaxAuthenticationFailureHandler" 
    class="me.co.server.web.resource.register.login.AjaxAuthenticationFailureHandler" /> 

<beans:bean id="myUserDetailsService" 
    class="me.co.server.bl.security.MyUserDetailsService" /> 

我加了安全上下文和安全过滤器,以我的web.xml的顶部:

<!-- Spring Security --> 
<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> 

<context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value> 
    /WEB-INF/spring/applicationContext.xml 
    /WEB-INF/spring/security-context.xml 
    </param-value> 
</context-param> 

这是我的网页层球衣的实施:

@Path("/secured/{resourceName}") 
@Component 
public class SecuredResourceProvider extends ServiceResourceProvider { 

    /*--- Members ---*/ 

    private ILogger logger = LogManager.getLogger(SecuredResourceProvider.class); 

    @Inject 
    protected SecuredResourceFactory securedResourceFactory; 

    /*--- Constructors ---*/ 

    protected SecuredResourceProvider() { 
    super("Secured"); 
    } 

    /*--- Public Methods ---*/ 

    @GET 
    @Produces("application/json") 
    @Path("/{resourceId}") 
    public String getSecuredResource(@PathParam("resourceId") String resourceId, @PathParam("resourceName") String resourceName) 
     throws UnhandledResourceException, UnauthorizedAccessException, ServerInternalErrorException, JsonException, ResourceArgumentException { 

    // NOT IMPLEMENTED AT THE MOMENT // 

    return null; 
    } 

    @PUT 
    @Consumes(MediaType.APPLICATION_JSON) 
    @Produces(MediaType.TEXT_PLAIN) 
    @Path("/{code}") 
    public String putSecuredResource(String resourceData, @PathParam("code") String ownerResourceId, @PathParam("resourceName") String resourceName) 
     throws UnhandledResourceException, UnauthorizedAccessException, ServerInternalErrorException, JsonException, ResourceArgumentException { 

    if (SecuredResources.isSecuredResource(resourceName)) { 
     IResource<String> resource = securedResourceFactory.getResourceInstance(resourceName); 

     String resourceString = resource.put(ownerResourceId, resourceData); 
     return createReturnResourceString(resourceString); 
    } else { 
     throw new UnhandledResourceException("Invoking a secured method for an unsecured resource " + resourceName); 
    } 
    } 


    @POST 
    @Consumes(MediaType.APPLICATION_JSON) 
    @Produces(MediaType.TEXT_PLAIN) 
    @Path("/{resourceId}") 
    public String postSecuredResource(String resourceData, @PathParam("resourceName") String resourceName, @PathParam("resourceId") String resourceId) 
     throws UnhandledResourceException, UnauthorizedAccessException, ServerInternalErrorException, JsonException, ResourceArgumentException { 

    if (SecuredResources.isSecuredResource(resourceName)) { 
     IResource<String> resource = securedResourceFactory.getResourceInstance(resourceName); 
     String resourceString = resource.post(resourceId, resourceData); 
     return createReturnResourceString(resourceString); 
    } else { 
     throw new UnhandledResourceException("Invoking a secured method for an unsecured resource " + resourceName); 
    } 
    } 


    @DELETE 
    @Consumes(MediaType.TEXT_PLAIN) 
    @Produces(MediaType.TEXT_PLAIN) 
    @Path("/{code}") 
    public String deleteSecuredResource(String resourceId, @PathParam("code") String ownerResourceId, @PathParam("resourceName") String resourceName) 
     throws UnhandledResourceException, UnauthorizedAccessException, ServerInternalErrorException, JsonException, ResourceArgumentException { 

    if (SecuredResources.isSecuredResource(resourceName)) { 
     IResource<String> resource = securedResourceFactory.getResourceInstance(resourceName); 

     String resourceString = resource.delete(ownerResourceId, resourceId); 
     return createReturnResourceString(resourceString); 
    } else { 
     throw new UnhandledResourceException("Invoking a secured method for an unsecured resource " + resourceName); 
    } 
    } 
} 

除了根据我的url的拦截定义,这个spring bean应该是安全的(需要通过身份验证的用户),这里没什么特别的。 web层将请求委托给商业逻辑层,这是使用方法的安全性注解担保:

@Override 
    @Secured("ROLE_BRAND_MANAGER") 
    public String post(String gymCode, String trainingSessionJson) throws UnhandledResourceException, ServerInternalErrorException, 
     ResourceArgumentException, JsonException { 

... 

} 

对于这项工作,我加入了全局方法的安全性声明我应用上下文

<security:global-method-security secured-annotations="enabled" proxy-target-class="true"/> 

,颁发的用户是品牌经理(据当局明智),但此刻的suthorities不会保留,但是从实体返回的硬编码:

@Entity 
@Table(name = "brand_managers") 
public class BrandManager implements Serializable, UserDetails { 

    /** Serial version unique id */ 
    private static final long serialVersionUID = -7992146584570782015L; 

    public static final String ROLE = "ROLE_BRAND_MANAGER"; 

    /*--- Members ---*/ 

    /** The unique, internal ID of the entity. */ 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private long id; 

    /** 
    * The creation time of this user 
    */ 
    @Column(name = "creation_time") 
    protected long creationTime; 

    /** 
    * The hashed password 
    */ 
    @Column(name = "password") 
    protected String password; 

    @Column(name = "first_name") 
    protected String firstName; 

    @Column(name = "last_name") 
    protected String lastName; 

    @Column(name = "email") 
    protected String eMail; 

    @Column(name = "address1") 
    protected String address1; 

    @Column(name = "address2", nullable = true) 
    protected String address2; 

    @Column(name = "city") 
    protected String city; 

    @Column(name = "state") 
    protected String state; 

    @Column(name = "zip", nullable = true) 
    protected String zip; 

    @Column(name = "country") 
    protected String country; 

    @Column(name = "phone") 
    protected String phone; 

    @Column(name = "brand_id") 
    protected int brandId; 

    /*--- Constructors ---*/ 

    /** 
    * default 
    */ 
    public BrandManager() { 
    setCreationTime(Calendar.getInstance().getTimeInMillis()); 
    } 

    public BrandManager(String password, String firstName, String lastName, String eMail, String address1, String address2, String city, 
     String state, String zip, String country, String phone, int brandId) { 
    this(); 
    this.password = password; 
    this.firstName = firstName; 
    this.lastName = lastName; 
    this.eMail = eMail; 
    this.address1 = address1; 
    this.address2 = address2; 
    this.city = city; 
    this.state = state; 
    this.zip = zip; 
    this.country = country; 
    this.phone = phone; 
    this.brandId = brandId; 
    } 

    /*--- Overridden Methods ---*/ 

    @Override 
    public Collection<? extends GrantedAuthority> getAuthorities() { 
    // currently not holding authorities in DB, but returning hard-coded 
    return AuthorityUtils.createAuthorityList(ROLE); 
    } 
... getter and setters... 

} 

我的UserDetailsS​​ervice简单地从DB加载用户(使用dao委托)。

现在,当我测试我的服务器(运行在tomcat中)并调用安全的URL时,我看到一些奇怪的东西。 我把一个破发点中的AffirmativeBased类的“决定”的方法,这是春季安全的DecisionManager的默认实现:

公共无效做决定(验证认证,Object对象,集合configAttributes) 抛出AccessDeniedException异常{ int deny = 0;

for (AccessDecisionVoter voter : getDecisionVoters()) { 
    int result = voter.vote(authentication, object, configAttributes); 

    if (logger.isDebugEnabled()) { 
     logger.debug("Voter: " + voter + ", returned: " + result); 
    } 

    switch (result) { 
    case AccessDecisionVoter.ACCESS_GRANTED: 
     return; 

    case AccessDecisionVoter.ACCESS_DENIED: 
     deny++; 

     break; 

    default: 
     break; 
    } 
} 

和调用我的服务方法,它是注释与@Secured注释时,我看到“configAttributes”属性包含的[permitAll]的值,这不是我希望看到一个元素。我期望看到“ROLE_BRAND_MANAGER”。我在调试错误的地方?我如何知道我的安全相关代码是正确的?

由于提前, 瑜珈

+2

第一条规则总是胜出,所以我建议重新排列'intercept-url'-s并将'permitAll'移动到最后。 – 2012-08-03 20:26:38

+0

那么,这让我感觉有点愚蠢:)我相信你是对的,我的安全限制被错误配置。我将有机会明天才能测试它。你能否发表你的评论作为答案?谢谢! – forhas 2012-08-04 06:47:17

回答

0

正如所建议的PHP编码器:

第一条规则总是赢,所以我建议重新排列拦截的URL-S和移动permitAll到底。

我不得不切换我的url-interception定义的顺序,现在流程才有意义。

1

其实有在这样的命名空间配置的两个AccessDecisionManager实例。一个用于Web安全性和一个用于方法安全性。如果需要,两者都可以被覆盖。

您的断点可能意味着您正在看到网络安全(如果您在调试器中检查堆栈,这应该很明显)。

确保global-method-security与您想要保护的bean位于相同的上下文文件中。在Spring Security FAQ中有更多的信息,你还可以找到关于父母/孩子春天上下文文件的其他讨论。通常,如果您想要保护在调度程序servlet配置文件(例如spring-servlet.xml)中声明的MVC控制器或其他Web Bean,那么它也应该放在那里。它们在主应用程序上下文中不可见(在web.xml中由ContextLoaderListener载入的应用程序上下文),所以如果将方法安全性元素放在那里,它将不起作用。

+0

嗨,我找不到任何指定存在AccessDecisionManager的2个不同实例的任何引用(你能指出吗?)。此外,由于相同的逻辑贯穿两者 - 网络层安全性和方法层安全性,因此有两种不同的实现方式是没有意义的。关于全球方法安全的位置 - 这是一个很好的提示,它在过去引起了麻烦,但我确信这次是正确的:) – forhas 2012-08-04 06:58:07

+0

行为不一定相同。例如,在这里您在web层使用表达式,因此配置将与方法安全性不同。对于'http'和'global-method-security',有一个单独的'access-decision-manager-ref'属性,您可以使用它来独立地定制每个属性。 – 2012-08-04 14:34:39

+0

好的,我找到了你。我刚开始时想到的是两位不同的选民(确实存在..),但我现在明白你在说什么,谢谢! – forhas 2012-08-05 13:01:45