Spring的核心是提供了一個 容器(container),通常稱爲Spring應用上下文(Spring application context),它們會創建和管理應用組件。將bean裝配在一起的行爲是通過一種基於 依賴注入(dependency injection,DI) 的模式實現的。Spring通過配置文件或者基於java配置將bean裝配在一起。
@Configuration註解會告知Spring這是一個配置類,會爲Spring應用上下文提供bean。這個配置類的方法使用@Bean註解進行了標註,表明這些方法所返回的對象會以bean的形式添加到Spring的應用上下文中(默認情況下,這些bean所對應的bean ID與定義它們的方法名稱是相同的)。但是需要注意的是無論使用Java還是使用xml的顯式配置,只有當Spring不能進行自動配置的時候纔是必要的。
自動配置起源於所謂的 自動裝配(autowiring) 和 組件掃描(component scanning)。藉助組件掃描技術,Spring能夠自動發現應用類路徑下的組件,並將它們創建成Spring應用上下文中的bean。藉助自動裝配技術,Spring能夠自動爲組件注入它們所依賴的其他bean。
Spring Boot starter依賴的特別之處在於它們本身並不包含庫代碼,而是傳遞性拉去其他的庫。這種starter依賴主要有3個好處:
構建文件會顯著減小並且更易於管理,因爲這樣不必爲每個所需的依賴庫都聲明依賴。
我們能夠根據它們所提供的功能來思考依賴,而不是根據庫的名稱。如果是開發Web應用,那麼你只需要添加web starter就可以了,而不必添加一堆單獨的庫再編寫Web應用。
我們不必再擔心庫版本的問題。你可以直接相信給定版本的Spring Boot,傳遞性引入的庫的版本是兼容的。現在,你只需要關心使用的是哪個版本的Spring Boot就可以。
構建規範還包含一個Spring Boot插件。這個插件提供了一些重要的功能:
- 它提供了一個Maven goal,允許我們使用Maven來運行應用。
- 它會確保依賴的所有庫都會包含在可執行JAR文件中,並且能夠保證它們在運行時類路徑下是可用的。
- 它會在JAR中生成一個manifest文件,將引導類聲明爲可執行JAR的主類。
@SpringBootApplication是一個組合註解,它組合了3個其他的註解。
- @SpringBootConfiguration:將該類聲明爲配置類。這個註解實際上是@Configuration註解的特殊形式。
- @EnableAutoConfiguration:啓用Spring Boot的自動配置。
- @ComponentScan:啓用組件掃描。這樣我們能夠通過像@Component、@Controller、@Service這樣的註解聲明其他類,Spring會自動發現它們並將它們註冊爲Spring應用上下文中的組件。
啓動類的main()方法:
public class BootAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(BootAdminServerApplication.class, args);
}
}這個main()方法會調用SpringApplication中靜態的run()方法,後者會真正執行應用的引導過程,也就是創建Spring的應用上下文。在傳遞給run()的兩個參數中,一個是配置類,另一個是命令行參數。
SpringBootTest:
@RunWith(SpringRunner.class)
@SpringBootTest@WebMvcTest(MvcController.class)
public class BootAdminServerApplicationTests {@Test
public void contextLoads() {
}}
- @RunWith(SpringRunner.class)註解:運行容器Spring,@RunWith是JUnit的註解,它會提供一個測試運行器(runner)來指導JUnit如何運行測試。SpringRunner是SpringJUnit4ClassRunner的別名。
- @SpringBootTest會告訴JUnit在啓動測試的時候要添加上Spring Boot的功能。
- @WebMvcTest註解。這是Spring Boot所提供的一個特殊測試註解,它會讓這個測試在Spring MVC應用的上下文中執行。除了使用該註解外,還需要注入MockMvc,Spring自帶了一個強大的Web框架,名爲Spring MVC。Spring MVC的核心是 控制器(controller) 的理念。控制器是處理請求並以某種方式進行信息響應的類。
Spring Boot DevTools提供了很多開發工具,包括:
- 代碼變更後應用的自動重啓;
- 當面向瀏覽器的資源等發生變化時,會自動刷新瀏覽器;
- 自動禁用模板緩存;
- 如果使用H2數據庫的話,內置了H2控制檯。
當探測到變更的時候,DevTools只會重新加載包含項目代碼的類加載器,並重啓Spring的應用上下文,在這個過程中另外一個類加載器和JVM會原封不動。這個策略非常精細,但是它能減少應用啓動的時間。 這種策略的一個不足之處就是自動重啓無法反映依賴項的變化。這是因爲包含依賴庫的類加載器不會自動重新加載。這意味着每當我們在構建規範中添加、變更或移除依賴的時候,爲了讓變更生效,我們需要重新啓動應用。
默認情況下,像Thymeleaf和FreeMarker這樣的模板方案在配置時會緩存模板解析的結果。
DevTools在運行的時候,它會和你的應用程序一起,同時自動啓動一個LiveReload服務器。LiveReload服務器本身並沒有太大的用處。但是,當它與LiveReload瀏覽器插件結合起來的時候,就能夠在模板、圖片、樣式表、JavaScript等(實際上,幾乎涵蓋爲瀏覽器提供服務的所有內容)發生變化的時候自動刷新瀏覽器。LiveReload有針對Google Chrome、Safari和Firefox的瀏覽器插件。
使用Lombok庫消除樣板代碼。如:@Data註解就是由Lombok提供的,它會告訴Lombok生成所有缺失的方法,同時還會生成所有以final屬性作爲參數的構造器。@Slf4j在運行時,它會在這個類中自動生成一個SLF4J(Simple Logging Facade for Java)Logger。@NoArgsConstructor: 註解在類,生成無參的構造方法;除了類級別註解還有方法或字段註解。詳情看:官方文檔。
Spring支持Java的Bean校驗API(Bean Validation API,也被稱爲JSR-303)。藉助Spring Boot,要在項目中添加校驗庫,我們甚至不需要做任何特殊的操作,這是因爲Validation API以及Validation API 的Hibernate實現將會作爲Spring Boot web starter的傳遞性依賴自動添加到項目中。
Spring Data爲所有項目提供了一項最有趣且最有用的特性,就是基於repository規範接口自動生成repository的功能。爲了將Ingredient聲明爲JPA實體,它必須添加@Entity註解。它的id屬性需要使用@Id註解,以便於將其指定爲數據庫中唯一標識該實體的屬性,可以通過@GeneratedValue設置id增長策略。JPA需要實體有一個無參構造器。Spring Data定義了一組小型的領域特定語言(Domain-Specific Language,DSL)。
保護Spring應用的第一步就是將Spring Boot security starter依賴添加到構建文件中。通過將security starter添加到項目的構建文件中,我們得到了如下的安全特性:
- 所有的HTTP請求路徑都需要認證;
- 不需要特定的角色和權限;
- 沒有登錄頁面;
- 認證過程是通過HTTP basic認證對話框實現的;
- 系統只有一個用戶,用戶名爲user。
Spring Security爲配置用戶存儲提供了多個可選方案,包括: 基於內存的用戶存儲; 基於JDBC的用戶存儲; 以LDAP作爲後端的用戶存儲; 自定義用戶詳情服務。無論採取何種用戶存儲方式,都需要繼承WebSecurityConfigurerAdapter類,並重寫 configure()方法進行配置。而且子類需要使用註解@Configuration、@EnableWebSecurity啓用配置。
1、基於內存的用戶存儲
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("buzz")
.password("infinity")
.authorities("ROLE_USER")
.and()
.withUser("woody")
.password("bullseye")
.authorities("ROLE_USER");
}2、基於JDBC的用戶存儲
@Autowired
DataSource dataSource;
//如果數據庫表設置與UserDetailsService默認實現中的結構一致@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
//如果數據庫表設置與UserDetailsService默認實現中的結構不一致@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery(
"select username, authority from UserAuthorities " +
"where username=?");
}//密碼加密情況
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery(
"select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
}【注意】
- 將默認的SQL查詢替換爲自定義的設計時,很重要的一點就是要遵循查詢的基本協議。所有查詢都將用戶名作爲唯一的參數。認證查詢會選取用戶名、密碼以及啓用狀態信息。權限查詢會選取零行或多行包含該用戶名及其權限信息的數據。羣組權限查詢會選取零行或多行數據,每行數據中都會包含羣組ID、羣組名稱以及權限。
- passwordEncoder()方法指定一個密碼轉碼器(encoder),passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意實現。Spring Security的加密模塊包括了多個這樣的實現,當然也可自定義實現。
- BCryptPasswordEncoder:使用bcrypt強哈希加密。
- NoOpPasswordEncoder:不進行任何轉碼。
- Pbkdf2PasswordEncoder:使用PBKDF2加密。
- SCryptPasswordEncoder:使用scrypt哈希加密。
- StandardPasswordEncoder:使用SHA-256哈希加密。
3、以LDAP(Lightweight Directory Access Protocol,輕量級目錄訪問協議)作爲後端的用戶存儲
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.ldapAuthentication()
.userSearchFilter("(uid={0})")
.groupSearchFilter("member={0}");
}【注意】基於LDAP認證的默認策略是進行綁定操作,直接通過LDAP服務器認證用戶。另一種可選的方式是進行比對操作。這涉及將輸入的密碼發送到LDAP目錄上,並要求服務器將這個密碼和用戶的密碼進行比對。因爲比對是在LDAP服務器內完成的,實際的密碼能保持私密。默認情況下,Spring Security的LDAP認證假設LDAP服務器監聽本機的33389端口。當LDAP服務器啓動時,它會嘗試在類路徑下尋找LDIF文件來加載數據。LDIF(LDAP Data Interchange Format,LDAP數據交換格式)是以文本文件展現LDAP數據的標準方式。每條記錄可以有一行或多行,每項包含一個name:value配對信息。記錄之間通過空行進行分割。
4、自定義用戶詳情服務
1、//通過實現UserDetails接口,能夠提供更多信息給框架,比如用戶都被授予了哪些權限以及用戶的賬號是否可用
public class User implements UserDetails {
}2、@Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}3、@Service
public class UserRepositoryUserDetailsService implements UserDetailsService {private UserRepository userRepo;
@Autowired
public UserRepositoryUserDetailsService(UserRepository userRepo) {
this.userRepo = userRepo;
}
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException("User '" + username + "' not found");
}
}
4、重寫configure()
@Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
可以使用HttpSecurity配置的功能包括:
- 在爲某個請求提供服務之前,需要預先滿足特定的條件;
- 配置自定義的登錄頁;
- 支持用戶退出應用;
- 預防跨站請求僞造。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers("/", "/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.csrf()
.ignoringAntMatchers("/h2-console/**")
.and()
.headers()
.frameOptions()
.sameOrigin();
}
跨站請求僞造(Cross-Site Request Forgery,CSRF)是一種常見的安全攻擊。它會讓用戶在一個惡意的Web頁面上填寫信息,然後自動(通常是祕密的)將表單以攻擊受害者的身份提交到另外一個應用上。爲了防止這種類型的攻擊,應用可以在展現表單的時候生成一個CSRF token,並放到隱藏域中,然後將其臨時存儲起來,以便後續在服務器上使用。在提交表單的時候,token將和其他的表單數據一起發送至服務器端。請求會被服務器攔截,並與最初生成的token進行對比。如果token匹配,那麼請求將會允許處理;否則,表單肯定是由惡意網站渲染的,因爲它不知道服務器所生成的token。Spring Security提供了內置的CSRF保護。更幸運的是,默認它就是啓用的,我們不需要顯式配置,我們唯一需要做的就是確保應用中的每個表單都要有一個名爲“_csrf”的字段,它會持有CSRF token。
確定用戶是誰常用的方式如下:
- 注入Principal對象到控制器方法中;
- 注入Authentication對象到控制器方法中,需要進行類型轉換。
- 使用SecurityContextHolder來獲取安全上下文;
- 使用@AuthenticationPrincipal註解來標註方法。@AuthenticationPrincipal非常好的一點在於它不需要類型轉換,同時能夠將安全相關的代碼僅僅侷限於註解本身。