在《Spring Security调研记录【一】--实现基本认证与Url权限控制》中,实现的页面的认证与授权(基于Url),但AJAX、Android等的异步Json请求也是非常常用功能。
本文章主要实现异步Json请求的登录、认证与授权
一、首先明确与本实例相关的Spring Security对象的作用
Spring Security是基于Filter实现,在Servlet前执行,所有一切为执行一系列的Filter。
1、DelegatingFilterProxy
DelegatingFilterProxy提供一个web.xml与application context的连接,把web.xml请求连接至FilterChainProxy进行处理,FilterChainProxy在Spring Context进行定义,能最大化应用Spring配置优势。
2、FilterChainProxy
FilterChainProxy中定义一系列按照顺序执行的Filter。具体的Filter请参看Spring Security Reference。与本实例相关的Filter:
UsernamePasswordAuthenticationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
每个Filter需要关联主要对象为AuthenticationManager、SecurityContextHolder、Authentication、AccessDecisionManager(本例暂不关注)。
3、UsernamePasswordAuthenticationFilter
本Filter仅有于登录。默认绑定登录的url为"/login"(可自定义),则request url为绑定值,才执行本Filter。登录成功则委托AuthenticationSuccessHandler执行,并生成Authentication,保存至SecurityContextHolder.getContext()中,执行后续Filter;如果失败则委托AuthenticationFailureHandler执行,清理session与cache,不执行后续Filter了。
UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter
本例自定义一个类RestfulUsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter,实现UsernamePasswordAuthenticationFilter的类似功能,只是提供方便的request url设置和组装新的AuthenticationFailureHandler和AuthenticationSuccessHandler。
4、RememberMeAuthenticationFilter
本Filter紧跟UsernamePasswordAuthenticationFilter,用于进行登录后的基于token的认证,如果认证成功则生成Authentication,保存至SecurityContextHolder.getContext()中。
5、AnonymousAuthenticationFilter
在UsernamePasswordAuthenticationFilter和RememberMeAuthenticationFilter中都不通过,未生成Authentication,AnonymousAuthenticationFilter则生成一个匿名用户的Authentication,并保存到securityContextHolder.getContext()中,为后续Filter提供Authentication。
6、ExceptionTranslationFilter
本Filter不做任何Authentication和Authorization的工作,只是解析Exception,做出相应的动作。本Filter先执行FilterSecurityInterceptor的过滤,捕捉异常和收集异常,如果为Authentication异常则委托AuthenticationEntryPoint去处理;如果为Authorization异常且用户不是匿名用户,则委托AccessDeniedHandler去处理;如果用户为匿名用户(Anonymous),且异常为Authorization异常,也委托AuthenticationEntryPoint去处理。
7、FilterSecurityInterceptor
大概是进行Authorization的Filter,待详。
8、AuthenticationManager
顾名思义,AuthenticationManager认证的管理器,提供多个AuthenticationProvider,本例就使用默认的DaoAuthenticationProvider。
9、AuthenticationEntryPoint
本接口只有一个方法,认证失败后执行。本例就实现该接口,实现认证失败后,返回Json字符串而不是跳转至登录页面。
10、AccessDeniedHandler
本接口只有一个方法,权限验证失败后执行。本例就实现该接口,实现权限不足时,返回Json字符串而不是返回403错误。
11、AuthenticationFailureHandler
本接口只有一个方法,登录验证失败后执行。本例就实现该接口,登录验证失败后,返回Json字符串而不是跳整到登录页面。
12、AuthenticationSuccessHandler
本接口只有一个方法,登录验证成功后执行。本例就实现该接口,登录验证成功后,返回Json字符串而不是跳整到默认页面或上一页面。
二、重定义Java对象
1、重定义登录验证过滤器
<span style="white-space:pre"> </span>public class RestfulUsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY = "password";
public static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/rest/login";
private String usernameParameter = SPRING_SECURITY_RESTFUL_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_RESTFUL_PASSWORD_KEY;
private boolean postOnly = true;
protected RestfulUsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public String getUsernameParameter() {
return usernameParameter;
}
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
public String getPasswordParameter() {
return passwordParameter;
}
public void setPasswordParameter(String passwordParameter) {
this.passwordParameter = passwordParameter;
}
public boolean isPostOnly() {
return postOnly;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public void setLoginUrl(String loginUrl) {
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(loginUrl, "POST"));
}
}
<span style="white-space:pre"> </span>public class RestfulAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
PrintWriter writer;
String returnStr = "{message:'sucess'}";
response.setStatus(200);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
requestCache.removeRequest(request, response);
clearAuthenticationAttributes(request);
}
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}
3、重实现AuthenticationFailureHandler
<span style="white-space:pre"> </span>public class RestfulAuthenticationFailureHandler implements
AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
PrintWriter writer;
String returnStr = "{exception:{name:'" + exception.getClass()
+ "',message:'" + exception.getMessage() + "'}}";
System.out.println(this.getClass().toString()+":"+returnStr);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
}
}
4、重实现AuthenticationEntryPoint
<span style="white-space:pre"> </span>public class RestfulAuthenticationEntryPoint implements
AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
PrintWriter writer;
String returnStr = "{exception:{name:'" + authException.getClass()
+ "',message:'" + authException.getMessage() + "'}}";
System.out.println(this.getClass().toString()+":"+returnStr);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
}
}
5、重实现AccessDeniedHandler
<span style="white-space:pre"> </span>public class RestfulAccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
PrintWriter writer;
String returnStr = "{exception:{name:'" + accessDeniedException.getClass()
+ "',message:'" + accessDeniedException.getMessage() + "'}}";
System.out.println(this.getClass().toString()+":"+returnStr);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
}
}
三、组装重定义的Java对象(在Spring Context中)
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<http pattern="/**/*.css" security="none" />
<http pattern="/**/*.js" security="none" />
<http pattern="/security/**/restful" entry-point-ref="restfulAuthenticationEntryPoint"
authentication-manager-ref="authenticationManager">
<access-denied-handler ref="restfulAccessDeniedHandlerImpl" />
<custom-filter position="FORM_LOGIN_FILTER"
ref="restfulUsernamePasswordAuthenticationFilter" />
<intercept-url pattern="/security/login/restful" access="permitAll()" />
<intercept-url pattern="/security/getJson/restful" access="hasRole('ADMIN')" />
<intercept-url pattern="/**" access="hasRole('USER')" />
<csrf disabled="true" />
</http>
<http pattern="/security/**">
<form-login login-page="/security/login.jsp"
login-processing-url="/security/login" default-target-url="/security/index"
always-use-default-target="false" authentication-failure-url="/security/login.jsp?error=wrong_login_data"
username-parameter="username" password-parameter="password" />
<logout logout-url="/security/logout" />
<intercept-url pattern="/security/index" access="permitAll()" />
<intercept-url pattern="/security/logout" access="permitAll()" />
<intercept-url pattern="/security/login.jsp" access="permitAll()" />
<csrf />
</http>
<global-method-security pre-post-annotations="enabled" />
<beans:bean name="bcryptEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref='myUserDetailsService'>
<password-encoder ref="bcryptEncoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="myUserDetailsService"
class="com.winssage.spring.security.userdetails.WinssageUserDetailsService">
<beans:property name="bcryptPasswordEncoder" ref="bcryptEncoder" />
</beans:bean>
<!-- restful security start -->
<beans:bean id="restfulUsernamePasswordAuthenticationFilter"
class="com.winssage.spring.security.authentication.RestfulUsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="restfulAuthenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="restfulAuthenticationFailureHandler" />
<beans:property name="loginUrl" value="/security/login/restful" />
</beans:bean>
<beans:bean id="restfulAuthenticationSuccessHandler"
class="com.winssage.spring.security.authentication.RestfulAuthenticationSuccessHandler">
</beans:bean>
<beans:bean id="restfulAuthenticationFailureHandler"
class="com.winssage.spring.security.authentication.RestfulAuthenticationFailureHandler">
</beans:bean>
<beans:bean id="restfulAuthenticationEntryPoint"
class="com.winssage.spring.security.authentication.RestfulAuthenticationEntryPoint" />
<beans:bean id="restfulAccessDeniedHandlerImpl"
class="com.winssage.spring.security.access.RestfulAccessDeniedHandlerImpl">
</beans:bean>
<!-- restful security end -->
</beans:beans>
四、web.xml中启动Spring Security
<span style="white-space:pre"> </span><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>
五、Maven依赖配置(pom.xml)
<span style="white-space:pre"> </span><properties>
...
<org.springframework-security-version>4.0.1.RELEASE</org.springframework-security-version>
</properties>
<dependencies>
...
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${org.springframework-security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${org.springframework-security-version}</version>
</dependency>
</dependencies>