1. 程式人生 > >Spring Security表單登入認證

Spring Security表單登入認證

Spring Security是一個強有力並且高度定製化的認證和訪問控制框架,致力於為Java應用程式提供認證和授權。


特性:
1、為認證和授權提供綜合性和擴充套件性支援。
2、免受session定位、點選劫持、跨站點請求偽裝等攻擊。
3、Servelt API整合。
4、與Spring MVC整合

一、Spring Security架構


1、認證


AuthenticationManager是主要的認證策略介面,該介面只包含一個方法。
public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;

}

認證管理器通過authenticate方法可做下面3處理: 
(1)如果能證明輸入是一個合理的使用者標識,則返回Authentication物件。
(2)如果輸入不是一個合理的使用者標識,丟擲AuthenticationExcepton。
(3)如果無法決策則返回null。
備註:AuthenticationException為執行時異常,根據需要進行處理。

AuthenticationManager常用的實現為ProviderManager,ProviderManager會委託給AuthenticationProvider例項鏈。
AuthenticationProvider和AuthenticataionManager相似,但提供了額外的方法允許呼叫者查詢是否支援給定的Authentication型別。

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;

    boolean supports(Class<?> authentication);
}

2、授權(訪問控制)


認證成功後就是授權,核心策略是AccessDecisionManager,框架提供了訪問決策管理器3種實現,它們都會委託給AccessDecisionVoter,就像
ProviderManager委託給AuthenticationProvider。

public interface AccessDecisionVoter {

    boolean supports(ConfigAttribute attribute);

    boolean supports(Class<?> clazz);

    int vote(Authentication authentication, S object,
            Collection<ConfigAttribute> attributes);
}

object引數代表使用者想要訪問的任何事物(如:web或者java類中的方法),ConfigAttributes代表訪問資源所需的許可證等級,
ConfigAttribute是一個介面,只有一個getAttribute()方法,返回值為誰允許訪問資源的表示式規則,例如使用者角色的名字:ROLE_ADMIN或者ROLE_AUDIT,一般以ROLE_字首開頭。

二、Web Security


1、Spring security在web層是基於Filters的,下面是單個http請求的處理層級:
Client <-> Filter <-> Filter <-> Filter <-> Servlet
客戶單傳送請求到app,由容器來決定使用哪些filters和servelt,一個請求最多一個servlet來處理,但能有多個filters,多個filters有排序,一個filter能否決其它剩餘filter,並且能修改下游filter
和servlet的請求與響應。

Spring security在過濾器鏈中註冊為單個過濾器,具體型別為FilterChainProxy,預設被註冊為@Bean來處理所有請求。下面是有過濾器鏈代理後單個http請求的處理層級:
Client <-> Filter <-> FilterChainProxy(到多個filters) <-> Filter <-> Servlet
備註:事實上在安全過濾器中還有隱含的一層,DelegatingFilterProxy,該代理會委託給FilterChainProxy(在容器中為一個@Bean,並且bean的名字固定為springSecurityFilterChain,該bean包含了所有
安全相關的邏輯)

2、FilterChainProxy下面可以有多個由spring security管理的過濾器鏈,並且會把請求分發到第一個匹配到的過濾器鏈,有且只有一個過濾器鏈來處理。
            FilterChainProxy
/foo/**            /bar/**            /**
Filter            Filter            Filter
Filter            Filter            Filter
Filter            Filter            Filter
注意:/foo/**會比/**先匹配到,匹配規則:精確匹配 > 路徑匹配 > 萬用字元匹配 > 預設匹配

3、一般沒有其它自定義的FilterChainProxy有5個或過濾器鏈,第一個用來忽略靜態資源,像/css/**,/images/**以及錯誤檢視/error。最後一個過濾器鏈匹配/**,
包含認證,授權,異常處理,會話處理,頭部寫入等等。

三、Java配置


1、表單登入認證

(1)配置類繼承自WebSecurityConfigurerAdapter,並帶上@EnableWebSecurity註解。

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()// 限制基於HttpServletReqeuest的請求訪問
          .antMatchers("/", "/home").permitAll()// /和/home路徑能被任何人訪問
          .anyRequest().authenticated()// 其它請求需要身份認證
          .anyRequest().hasRole("USER")// 其它請求必須是USER角色,該方法預設會加上ROLE_字首
        .and()
          .formLogin()// 支援基於表單的身份認證
          .loginPage("/login")// 指定跳轉到登入頁的url,若不指定則會生成預設登入頁面,預設為/login
          .loginProcessingUrl("/loginProcess")// 指定認證處理的url,表單action指定地址必須為該地址,預設為/login
          .defaultSuccessUrl("/success")// 認證成功後預設跳轉的地址,預設為/home
          .failureUrl("/loginProcess?error")// 認證失敗後跳轉的地址,預設為/login?error
          .permitAll()// 給使用者所有與表單登入相關的url訪問授權
        .and()
          .rememberMe()// 開啟記住我的功能
          .rememberMeCookieName("remember-me")// 傳給瀏覽器的cookie名,預設為remember-me
          .rememberMeParameter("remember-me")// 前端複選框傳入的欄位名,預設為remember-me
          .tokenValiditySeconds(30 * 1000)// cookie有效時間
          .key(UUID.randomUUID().toString())// 防止名為remember-me的token被修改的key,預設為隨機數,生成隨機數需要時間,最好指定固定值
        .and()
          .logout()// 開啟退出登入支援
          .logoutUrl("/logout")// 觸發退出登入的url,前端頁面地址必須為改地址,預設為/logout
          .logoutSuccessUrl("/loginProcess?logout")// 退出登入跳轉的地址
          .deleteCookies("remember-me")// 退出登入後刪除名為remember-me的cookie,預設會刪除remember-me功能對應的cookie
          .permitAll()// 授權所有與退出登入相關的url
        .and()
          .csrf().disable();// 禁用csrf,不禁用則要在表單里加上隱藏域或csrf標籤
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 建立DelegatingPasswordEncoder,該PasswordEncoder會使用BCryptPasswordEncoder對密碼進行編碼
    PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String encodedPwd = encoder.encode("123");
    // 傳入的密碼不能為原密碼,必須經過編碼,編碼後的格式為{編碼器Id}+編碼後的密碼
    auth.inMemoryAuthentication().withUser("lyl").password(encodedPwd).roles("USER");
  }

  /**
   * 不重寫configure(AuthenticationManagerBuilder auth)方法可以在容器中註冊UserDetailService例項
   */
  // @Bean
  // public UserDetailsService userDetailsService() {
  // String encodedPwd = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123");
  // UserDetails userDetails = User.withUsername("lyl").password(encodedPwd).roles("USER").build();
  // return new InMemoryUserDetailsManager(userDetails);
  // }
  
}

(2)Web MVC相關配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/success").setViewName("success");
    registry.addViewController("login").setViewName("login");
  }

  @Override
  public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.jsp("/WEB-INF/jsp/", ".jsp");
  }

}

@Controller
public class AuthController {

  @RequestMapping("/loginProcess")
  public String handelRequest(String error, String logout) {
    // 認證失敗重新跳轉到登入頁面
    if (error != null) {
      return "login";
    }

    // 如果是退出登入,則跳轉到退出登入後的頁面
    if (logout != null) {
      return "logout";
    }

    return "success";
  }
}

(3)前端jsp表單

<!-- 注意:請求方式必須為post,且action地址必須為loginProcessingUrl配置的地址 -->
<form action="loginProcess" method="post">
	<!-- 如果沒禁用csrf則要加上隱藏域,用於傳csrf token給後臺-->
	<!-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> -->
	<table>
		<tr>
			<td>使用者名稱:</td>
			<td><input name="username" type="text" /></td>
		</tr>
		<tr>
			<td>密碼:</td>
			<td><input name="password" type="password" /></td>
		</tr>
		<tr>
			<!-- 上傳欄位名必須和後端Java配置一致,預設為remember-me,使用者名稱和密碼也必須與後端配置保持一致,預設為username和password-->
			<td>記住我<input name="remember-me" type="checkbox" checked="checked" /></td>
		</tr>
		<tr>
			<td><input type="submit" value="登入"></td>
		</tr>
	</table>
</form>