2

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

这是我的代码,

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

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

...

}

为此,我将 global-method-security 声明添加到我的application-context

<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 简单地从数据库加载用户(使用 dao 委托)。

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

公共无效决定(身份验证,对象对象,集合 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”。我在调试错误的地方吗?我怎么知道我的安全相关代码是正确的?

在此先感谢,瑜伽士

4

2 回答 2

1

在这样的命名空间配置中实际上有两个AccessDecisionManager实例。一种用于网络安全,一种用于方法安全。如果需要,两者都可以被覆盖。

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

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

于 2012-08-03T23:35:40.947 回答
0

正如 php-coder 所建议的:

第一条规则总是获胜,所以我建议重新排序 intercept-url-s 并将 permitAll 移动到最后。

我不得不切换我的 url 拦截定义的顺序,现在流程变得有意义了。

于 2012-08-06T06:58:33.830 回答