關於 Spring Security 的學習已經告一段落了,剛開始接觸該安全框架感受很迷茫,總以爲沒有 Shiro 靈活,到後來的深刻學習和探究才發現它很是強大。簡單快速集成,基本不用寫任何代碼,拓展起來也很是靈活和強大。
集成完該框架默認狀況下,系統幫咱們生成一個登錄頁,默認除了登錄其餘請求都須要進行身份認證,沒有身份認證前的任何操做都會跳轉到默認登陸頁。
默認生成的密碼也會在控制檯輸出。html
接下來咱們可能須要本身控制一下權限,自定義一下登陸界面前端
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .formLogin() .loginPage("/login.html") //自定義登陸界面 .loginProcessingUrl("/login.action") //指定提交地址 .defaultSuccessUrl("/main.html") //指定認證成功跳轉界面 //.failureForwardUrl("/error.html") //指定認證失敗跳轉界面(注: 轉發須要與提交登陸請求方式一致) .failureUrl("/error.html") //指定認證失敗跳轉界面(注: 重定向須要對應的方法爲 GET 方式) .usernameParameter("username") //username .passwordParameter("password") //password .permitAll() .and() .logout() .logoutUrl("/logout.action") //指定登出的url, controller裏面不用寫對應的方法 .logoutSuccessUrl("/login.html") //登出成功跳轉的界面 .permitAll() .and() .authorizeRequests() .antMatchers("/register*").permitAll() //設置不須要認證的 .mvcMatchers("/main.html").hasAnyRole("admin") .anyRequest().authenticated() //其餘的所有須要認證 .and() .exceptionHandling() .accessDeniedPage("/error.html"); //配置權限失敗跳轉界面 (注: url配置不會被springmvc異常處理攔截, 可是註解配置springmvc異常機制能夠攔截到) }
從上面配置能夠看出自定義配置能夠簡單地分爲四個模塊(登陸頁面自定義、登出自定義、權限指定、異常設定),每一個模塊都對應着一個過濾器,詳情請看 Spring Security 進階-原理篇spring
須要注意的是:數據庫
loginProcessingUrl(..)
、登出URL logoutUrl(..)
都是對應攔截器的匹配地址,會在對應的過濾器裏面執行相應的邏輯,不會執行到 Controller 裏面的方法。defaultSuccessUrl(..)
、登陸認證失敗跳轉的URL failureUrl(..)
、登陸認證失敗轉發的URL failureForwardUrl(..)
......以及下面登出和權限配置的URL 能夠是靜態界面地址,也能夠是 Controller 裏面對應的方法。WebAttributes.AUTHENTICATION_EXCEPTION
來取出,可是前提是使用系統提供的身份認證異常處理handler SimpleUrlAuthenticationFailureHandler
。AccessDeniedHandlerImpl
。大多數開發狀況下都是先後端分離,響應也都是異步的,不是上面那種表單界面的響應方式,雖然經過上面跳轉到URL對應的 Controller 裏面的方法也能解決,可是大多數狀況下咱們須要的是極度簡化,這時候一些自定義的處理 handler 就油然而生。segmentfault
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .formLogin() .loginProcessingUrl("/login") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 身份認證成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("****** 身份認證失敗 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 登出成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .permitAll() .and() .authorizeRequests() .antMatchers("/main").hasAnyRole("admin") .and() .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { System.out.println("****** 沒有進行身份認證 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("****** 沒有權限 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }); }
注意:後端
logoutSuccessHandler(..)
,登陸身份認證失敗的 handler failureHandler(..)
,以避免默認這樣兩個步驟向不存在的登陸頁跳轉。failureHandler(..)
和 沒有進行身份認證的異常 handler authenticationEntryPoint(..)
,這兩個有區別,前者是在認證過程當中出現異常處理,後者是在訪問須要進行身份認證的URL時沒有進行身份認證異常處理。開發的時候咱們須要本身來實現登陸登出的流程,下面來個最簡單的自定義。安全
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("****** 登出成功 ******"); response.setStatus(HttpStatus.OK.value()); } }) .permitAll() .and() .authorizeRequests() .antMatchers("/main").hasAnyRole("admin") .and() .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { System.out.println("****** 沒有進行身份認證 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("****** 沒有權限 ******"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } }) .and() .addFilterBefore(new LoginFilter(), UsernamePasswordAuthenticationFilter.class); }
注意:session
SecurityContextHolder.clearContext();
,可是建議配置,通常登陸和登出最好都在過濾器裏面進行處理。自定義登陸過濾器詳情mvc
public class LoginFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; if ("/login".equals(httpServletRequest.getServletPath())) { //開始登陸過程 String username = httpServletRequest.getParameter("username"); String password = httpServletRequest.getParameter("password"); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password); //模擬數據庫查出來的 User.UserBuilder userBuilder = User.withUsername(username); userBuilder.password("123"); userBuilder.roles("user", "admin"); UserDetails user = userBuilder.build(); if (user == null) { System.out.println("****** 自定義登陸過濾器 該用戶不存在 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } if (!user.getUsername().equals(authentication.getPrincipal())) { System.out.println("****** 自定義登陸過濾器 帳號有問題 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } if (!user.getPassword().equals(authentication.getCredentials())) { System.out.println("****** 自定義登陸過濾器 密碼有問題 ******"); httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user.getUsername(), authentication.getCredentials(), user.getAuthorities()); result.setDetails(authentication.getDetails()); //注: 最重要的一步 SecurityContextHolder.getContext().setAuthentication(result); httpServletResponse.setStatus(HttpStatus.OK.value()); } else { chain.doFilter(request, response); } } }
注意:框架