我已经使用 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...
}
我的 UserDetailsService 简单地从数据库加载用户(使用 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”。我在调试错误的地方吗?我怎么知道我的安全相关代码是正确的?
在此先感谢,瑜伽士