本文讲述一下如何自定义spring security的登陆页,网上给的资料大多过期,并且是基于后端模板技术的,讲的不是太清晰,本文给出一个采用ajax的登陆及返回的先后端分离方式。css
总共须要处理3个地方,一个是异常的处理,须要兼容ajax请求,一个是成功返回的处理,一个是失败返回的处理。html
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前端
public class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); } }
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"); } }
@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"); } }
这里有几个要注意的点:java
这里要添加前端资源路径,以及登录表单请求的接口地址/loginweb
这里设置登陆页面的地址,这里咱们用静态页面,即static目录下的login.htmlajax
将authenticationEntryPoint,successHandler,failureHandler设置为上面自定义的ajax处理类spring
就是一个纯粹的html页面,其中登陆按钮的ajax请求以下:segmentfault
$.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,没关系么。下面来剖析一下。后端
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的。api
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配置。