转自: http://my.oschina.net/jilujia/blog/66795
使用spring security时遇到一个问题,有大量的ajax post是需要登录控制的,但是默认的spring-security机制导致post结果返回的是登录页。
现在要解决几个问题:
1,ajax post如果需要登录的话,返回需要登录的json消息,前端可以继续处理
2,新建一套ajax login的页面流转,但是不能和原有的login过程冲突,因为其他的非ajax请求还是需要用正常的login。
spring security配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:security
=
"http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<!-- Configure Spring Security -->
<!--
<security:http auto-config="true">
<security:form-login login-page="/login" login-processing-url="/loginProcess"
default-target-url="/" authentication-failure-url="/login?login_error=1" />
<security:logout logout-url="/logout" logout-success-url="/logoutSuccess" />
<security:remember-me key="bookingtest" />
</security:http>
-->
<
security:http
auto-config
=
"false"
entry-point-ref
=
"jilujiaAuthenticationEntryPoint"
>
<!-- 登录过滤器 -->
<
security:custom-filter
before
=
"FORM_LOGIN_FILTER"
ref
=
"loginFilter"
/>
<!-- ajax登录过滤器 -->
<
security:custom-filter
position
=
"FORM_LOGIN_FILTER"
ref
=
"ajaxLoginFilter"
/>
<!-- 只cache get,避免ajax post 被cache -->
<
security:request-cache
ref
=
"httpSessionRequestCache"
/>
<!-- 注销过滤器 -->
<
security:logout
logout-url
=
"/logout"
logout-success-url
=
"/logoutSuccess"
/>
<!-- remember me -->
<
security:remember-me
key
=
"bookingtest"
/>
</
security:http
>
<
bean
id
=
"jilujiaAuthenticationEntryPoint"
class
=
"com.jilujia.framework.security.JilujiaAuthenticationEntryPoint"
>
<
property
name
=
"loginFormUrl"
value
=
"/login"
/>
</
bean
>
<
bean
id
=
"httpSessionRequestCache"
class
=
"org.springframework.security.web.savedrequest.HttpSessionRequestCache"
>
<
property
name
=
"justUseSavedRequestOnGet"
value
=
"true"
/>
</
bean
>
<!-- 验证普通用户 -->
<
bean
id
=
"loginFilter"
class
=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
>
<
property
name
=
"authenticationManager"
ref
=
"authenticationManager"
/>
<
property
name
=
"authenticationFailureHandler"
ref
=
"failureHandler"
/>
<
property
name
=
"authenticationSuccessHandler"
ref
=
"successHandler"
/>
<
property
name
=
"filterProcessesUrl"
value
=
"/loginProcess"
/>
</
bean
>
<
bean
id
=
"failureHandler"
class
=
"org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
>
<
property
name
=
"defaultFailureUrl"
value
=
"/login?login_error=1"
/>
</
bean
>
<
bean
id
=
"successHandler"
class
=
"org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
>
<
property
name
=
"alwaysUseDefaultTargetUrl"
value
=
"false"
/>
<
property
name
=
"defaultTargetUrl"
value
=
"/"
/>
</
bean
>
<!-- 验证ajax请求-->
<
bean
id
=
"ajaxLoginFilter"
class
=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
>
<
property
name
=
"authenticationManager"
ref
=
"authenticationManager"
/>
<
property
name
=
"authenticationFailureHandler"
ref
=
"ajaxFailureHandler"
/>
<
property
name
=
"authenticationSuccessHandler"
ref
=
"ajaxSuccessHandler"
/>
<
property
name
=
"filterProcessesUrl"
value
=
"/ajaxLoginProcess"
/>
</
bean
>
<
bean
id
=
"ajaxFailureHandler"
class
=
"com.jilujia.framework.security.AjaxAuthenticationFailureHandler"
>
</
bean
>
<
bean
id
=
"ajaxSuccessHandler"
class
=
"com.jilujia.framework.security.AjaxAuthenticationSuccessHandler"
>
</
bean
>
<
security:global-method-security
jsr250-annotations
=
"enabled"
secured-annotations
=
"enabled"
/>
<
security:authentication-manager
alias
=
"authenticationManager"
>
<
security:authentication-provider
user-service-ref
=
"customUserDetailsService"
>
<
security:password-encoder
ref
=
"passwordEncoder"
/>
</
security:authentication-provider
>
</
security:authentication-manager
>
<
bean
id
=
"customUserDetailsService"
class
=
"com.jilujia.framework.security.JilujiaUserDetailsService"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
/>
</
bean
>
<
bean
id
=
"passwordEncoder"
class
=
"org.springframework.security.authentication.encoding.Md5PasswordEncoder"
/>
</
beans
>
|
重点有几个:jilujiaAuthenticationEntryPoint,解决问题1, 这里区分ajax请求和非ajax请求的方式是uri中包含不包含ajax字符串,可以按需调整。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public
class
JilujiaAuthenticationEntryPoint
extends
LoginUrlAuthenticationEntryPoint {
private
static
final
Log logger = LogFactory.getLog(JilujiaAuthenticationEntryPoint.
class
);
private
RedirectStrategy redirectStrategy =
new
DefaultRedirectStrategy();
public
void
commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws
IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String redirectUrl =
null
;
String url = request.getRequestURI();
if
(logger.isDebugEnabled()) {
logger.debug(
"url:"
+ url);
}
// 非ajax请求
if
(url.indexOf(
"ajax"
) == -
1
) {
if
(
this
.isUseForward()) {
if
(
this
.isForceHttps() &&
"http"
.equals(request.getScheme())) {
// First redirect the current request to HTTPS.
// When that request is received, the forward to the login page will be used.
redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest);
}
if
(redirectUrl ==
null
) {
String loginForm = determineUrlToUseForThisRequest(httpRequest, httpResponse, authException);
if
(logger.isDebugEnabled()) {
logger.debug(
"Server side forward to: "
+ loginForm);
}
RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return
;
}
}
else
{
// redirect to login page. Use https if forceHttps true
redirectUrl = buildRedirectUrlToLoginPage(httpRequest, httpResponse, authException);
}
redirectStrategy.sendRedirect(httpRequest, httpResponse, redirectUrl);
}
else
{
// ajax请求,返回json,替代redirect到login page
if
(logger.isDebugEnabled()) {
logger.debug(
"ajax request or post"
);
}
ObjectMapper objectMapper =
new
ObjectMapper();
response.setHeader(
"Content-Type"
,
"application/json;charset=UTF-8"
);
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
JsonData jsonData =
new
JsonData(
2
,
null
);
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch
(JsonProcessingException ex) {
throw
new
HttpMessageNotWritableException(
"Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
}
|
第二个问题,注意配置一个新的过滤器专门处理ajax 请求,这个filter是通过filterProcessesUrl=ajaxLoginProcess来区分ajax login动作和普通login动作的。
<!-- ajax登录过滤器 -->
<security:custom-filter position="FORM_LOGIN_FILTER" ref="ajaxLoginFilter"/>
<bean id="ajaxLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureHandler" ref="ajaxFailureHandler"/>
<property name="authenticationSuccessHandler" ref="ajaxSuccessHandler"/>
<property name="filterProcessesUrl" value="/ajaxLoginProcess"/>
</bean>
同时对应了两个handler,专门处理ajax登录的成功和失败,都返回json消息。
<bean id="ajaxFailureHandler" class="com.jilujia.framework.security.AjaxAuthenticationFailureHandler">
</bean>
<bean id="ajaxSuccessHandler" class="com.jilujia.framework.security.AjaxAuthenticationSuccessHandler">
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public
class
AjaxAuthenticationSuccessHandler
implements
AuthenticationSuccessHandler {
public
AjaxAuthenticationSuccessHandler() {
}
public
void
onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws
IOException, ServletException {
ObjectMapper objectMapper =
new
ObjectMapper();
response.setHeader(
"Content-Type"
,
"application/json;charset=UTF-8"
);
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
//成功为0
JsonData jsonData =
new
JsonData(
0
,
null
);
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch
(JsonProcessingException ex) {
throw
new
HttpMessageNotWritableException(
"Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
public
class
AjaxAuthenticationFailureHandler
implements
AuthenticationFailureHandler {
protected
final
Log logger = LogFactory.getLog(getClass());
public
AjaxAuthenticationFailureHandler() {
}
public
void
onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws
IOException, ServletException {
ObjectMapper objectMapper =
new
ObjectMapper();
response.setHeader(
"Content-Type"
,
"application/json;charset=UTF-8"
);
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
//失败为1
JsonData jsonData =
new
JsonData(
1
,
null
);
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch
(JsonProcessingException ex) {
throw
new
HttpMessageNotWritableException(
"Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
|
ajax login page差不多是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<
div
id
=
"inlineLogin"
style
=
"width:500px;display: none;"
>
<
form
id
=
"LoginForm"
action
=
"<c:url value="
/ajaxLoginProcess" />" method="post">
<
fieldset
>
<
legend
>Login Information</
legend
>
<
p
>
<
label
for
=
"j_username"
>User:</
label
>
<
br
/>
<
input
type
=
"text"
name
=
"j_username"
id
=
"j_username"
<c:if
test
=
"${not empty param.login_error}"
>value="<%= session.getAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY) %>"</
c:if
> />
</
p
>
<
p
>
<
label
for
=
"j_password"
>Password:</
label
>
<
br
/>
<
input
type
=
"password"
name
=
"j_password"
id
=
"j_password"
/>
</
p
>
<
p
>
<
input
type
=
"checkbox"
name
=
"_spring_security_remember_me"
id
=
"remember_me"
/>
<
label
for
=
"remember_me"
>Don't ask for my password for two weeks:</
label
>
</
p
>
<
p
>
<
a
href
=
"javascript:loginSubmit()"
id
=
'btn_login'
class
=
'rndbutton'
><
span
>Login</
span
></
a
>
</
p
>
</
fieldset
>
</
form
>
</
div
>
|
1
2
3
4
5
6
7
8
9
10
|
function
loginSubmit(){
var
form = $(
'#LoginForm'
).serialize();
$.post(
'<c:url value="/ajaxLoginProcess" />'
,form,
function
(data){
if
(data.error == 1)
alert(data.messages);
else
if
(data.error == 0){
alert(
"success"
)
}
});
}
|
特别注意的是配置了一个
<!-- 只cache get,避免ajax post 被cache -->
<security:request-cache ref="httpSessionRequestCache"/>
因为我的环境中所有的post都是ajax,这些都不需要cache。
参考:
http://www.360doc.com/content/12/0712/13/7656232_223767530.shtml
http://blog.csdn.net/zjh527/article/details/6158706