序
本文讲述一下如何自定义spring security的登录页,网上给的资料大多过时,而且是基于后端模板技术的,讲的不是太清晰,本文给出一个采用ajax的登录及返回的前后端分离方式。
ajax返回
总共需要处理3个地方,一个是异常的处理,需要兼容ajax请求,一个是成功返回的处理,一个是失败返回的处理。
ajax的异常处理
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
if(isAjaxRequest(request)){
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage());
}else{
response.sendRedirect("/login.html");
}
}
public static boolean isAjaxRequest(HttpServletRequest request) {
String ajaxFlag = request.getHeader("X-Requested-With");
return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
}
}
这里我们自定义成功及失败的ajax返回,当然这里我们简单处理,只返回statusCode
AjaxAuthSuccessHandler
public class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
AjaxAuthFailHandler
public class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
}
}
security配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint())
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/login","/css/**", "/js/**","/fonts/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("password")
.successHandler(new AjaxAuthSuccessHandler())
.failureHandler(new AjaxAuthFailHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
这里有几个要注意的点:
- permitAll
这里要添加前端资源路径,以及登陆表单请求的接口地址/login
- loginPage
这里设置登录页面的地址,这里我们用静态页面,即static目录下的login.html
- ajax配置
将authenticationEntryPoint,successHandler,failureHandler设置为上面自定义的ajax处理类
登录页面
就是一个纯粹的html页面,其中登录按钮的ajax请求如下:
$.ajax({
url: '/login',
type: 'POST',
data: "name="+name+"&password="+password,
success: function (res, status) {
window.location.href='/ok.html'
},
error: function (res, status) {
dangerDialog(res.statusText);
}
});
这里是请求/login,也就是spring security会默认拦截的路径,不了解spring security的人可能会纳闷,我请求这个路径,但是工程里头没有定义/login的request mapping,不要紧么。下面来剖析一下。
spring security内置的各种filter:
Alias | Filter Class | Namespace Element or Attribute |
---|---|---|
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
HEADERS_FILTER | HeaderWriterFilter | http/headers |
CSRF_FILTER | CsrfFilter | http/csrf |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter Subclasses | N/A |
CAS_FILTER | CasAuthenticationFilter | N/A |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | N/A |
这里我们要关注的就是这个UsernamePasswordAuthenticationFilter,顾名思义,它是filter,在执行/login请求的时候拦截,因而是不需要工程里头去定义login的request mapping的。
UsernamePasswordAuthenticationFilter
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
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);
}
//......
}
这里就是拦截,获取login.html提交的参数,然后交给authenticationManager去认证。之后就是走后续的filter,如果成功,则会进行相应的session配置。