原创文章,欢迎转载!转载时务必保留:作者:jmppok ;出处http://blog.csdn.net/jmppok/article/details/44832641
1.问题
在一个Web项目中一般会有两部分组成:
1)静态资源, 如html页面, js脚本,图片等等.
2)API接口。
在进行权限控制时,需要对这两部分进行统一管理。
Spring框架本身提供了强大的Security机制,但是在使用过程中还是会出现以下问题:
当用户访问一个页面时,页面本身经常是既包含静态资源,又需要调用API接口。
大家都知道,如果Spring Security判断当前用户没有权限访问某个资源时,会根据我们的配置自动跳转到Login页面或者403页面。
但实际上这可能并不是我们想要的:因为对于静态资源来说,浏览器一般会进行缓存,一旦缓存后就不会再向服务器请求,也就是说即使没有登陆或权限,静态页面也有可能被显示出来;但这时候对服务段的API调用可能是失败的。如前面所说,API调用失败时,会自动调转到会根据我们的配置自动跳转到Login页面或者403页面(注意这里是一个页面),而这并不是我们想要的结果。
因为对于API的服务调用来说,应该返回一个Json或者xml的结果,通过结果来判断调用成功还是失败以及失败原因是没有登陆或没有权限。然后根据调用失败信息,再判断是否跳转到登陆页面。而不是在调用API时由后台直接来返回一个Login.html或403.jsp.
2.解决办法
1)配置统一的权限管理
在applicationContext-security.xml中配置:
<http auto-config="false" entry-point-ref="myAuthenticationProcessingFilterEntryPoint" >
<intercept-url pattern="/login**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/js/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/images/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.html" authentication-failure-url ="/loginfailure" default-target-url="/loginsuccess"/>
<logout invalidate-session="true" logout-success-url="/login.html" logout-url="/j_spring_security_logout"/>
<access-denied-handler ref="myAuthenticationFailureHandler"/>
</http>
<beans:bean id="myAuthenticationProcessingFilterEntryPoint" class="com.lenovo.MyAuthenticationProcessingFilterEntryPoint" >
<beans:property name="loginFormUrl" value="/login.html"></beans:property>
</beans:bean>
<pre name="code" class="html"> <beans:bean id="myAuthenticationFailureHandler" class="com.lenovo.MyAuthenticationFailureHandler" />
<!-- 认证管理器。用户名密码都集成在配置文件中 -->
<authentication-manager >
<authentication-provider>
<user-service>
<user name="admin" password="admin" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
主要包括以下两点:
1)自定义EntryPoint,在Spring自动跳转到登陆节目时,进行自己的处理
entry-point-ref="myAuthenticationProcessingFilterEntryPoint"
<beans:bean id="myAuthenticationProcessingFilterEntryPoint" class="com.lenovo.MyAuthenticationProcessingFilterEntryPoint" >
<beans:property name="loginFormUrl" value="/login.html"></beans:property>
</beans:bean>
MyAuthenticationProcessingFilterEntryPoint 继承 LoginUrlAuthenticationEntryPoint 并重写其commence方法。
在commence中对请求进行判断, 比如判断是否ajax请求(代码中的视线)。 也可以判断是否某个URL,如/api/**等。
对于符合条件的直接返回一个Json数据串,表示没有登陆,访问失败。
其他的请求调用super.commence()按Spring原来的方式进行处理。
public class MyAuthenticationProcessingFilterEntryPoint extends LoginUrlAuthenticationEntryPoint {
/**
* @author ligh4 2015年4月1日下午4:38:04
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException,
ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if ("XMLHttpRequest".equals(httpRequest.getHeader("X-Requested-With"))) {
Map<String, Object> error = new HashMap<String, Object>();
error.put("success", false);
error.put("result", "loginfailure");
error.put("data", "loginfailure"); // 兼容extjs form loading
//RenderUtils.renderJSON((HttpServletResponse) response, error);
response.setContentType("json");
String json = new Gson().toJson(error);
response.getWriter().write(json);
response.getWriter().flush();
} else {
super.commence(request, response, authException);
}
}
}
2) 添加拒绝访问AccessDeniedHandler处理
<access-denied-handler ref="myAuthenticationFailureHandler"/>
<pre name="code" class="html"><beans:bean id="myAuthenticationFailureHandler" class="com.lenovo.MyAuthenticationFailureHandler" />
需要注意的是,AccessDeniedHandler只有在已Login而没有权限的情况下才被触发。 如果是session超时或者没有登陆,Spring则会直接跳转到登陆页面。
public class MyAuthenticationFailureHandler implements AccessDeniedHandler {
/**
* @author ligh4 2015年3月31日下午4:15:59
*/
@Override
public void handle(HttpServletRequest arg0, HttpServletResponse arg1, AccessDeniedException arg2)
throws IOException, ServletException {
LogHelper.debug(this, "handler AccessDeniedException...");
HttpServletRequest httpRequest = arg0;
// is ajax request?
if ("XMLHttpRequest".equals(httpRequest.getHeader("X-Requested-With"))) {
String msg = "{\"success\" : false, \"message\" : \"authentication-failure\"}";
arg1.setContentType("json");
OutputStream outputStream = arg1.getOutputStream();
outputStream.write(msg.getBytes());
outputStream.flush();
}
}
}
AccessDeniedHandler的处理同上面的MyAuthenticationProcessingFilterEntryPoint类似。
判断是否ajax或者api调用请求,如果是直接返回json数据。否则按Spring原来方式进行处理。