本文講述一下如何自定義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配置。