Spring Security 部分詳解
簡介:
Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面程式設計)功能,為應用系統提供宣告式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重複程式碼的工作。
SpringSecurity核心功能:
- 認證(你是誰)
- 授權(你能幹什麼)
- 攻擊防護(防止偽造身份)
1 核心元件
1.1 SecurityContextHolder
SecurityContextHolder用於儲存安全上下文(security context)的資訊。當前操作的使用者是誰,該使用者是否已經被認證,他擁有哪些角色許可權…這些都被儲存在SecurityContextHolder中。
SecurityContextHolder預設使用ThreadLocal 策略來儲存認證資訊。看到ThreadLocal 也就意味著,這是一種與執行緒繫結的策略。Spring Security在使用者登入時自動繫結認證資訊到當前執行緒,在使用者退出時,自動清除當前執行緒的認證資訊。但這一切的前提,是你在web場景下使用Spring Security,而如果是Swing介面,Spring也提供了支援,SecurityContextHolder的策略則需要被替換,鑑於我的初衷是基於web來介紹Spring Security,所以這裡以及後續,非web的相關的內容都一筆帶過。
獲取當前使用者的資訊
因為身份資訊是與執行緒繫結的,所以可以在程式的任何地方使用靜態方法獲取使用者資訊。一個典型的獲取當前登入使用者的姓名的例子如下所示:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); }
getAuthentication()返回了認證資訊,再次getPrincipal()返回了身份資訊,UserDetails便是Spring對身份資訊封裝的一個介面。Authentication和UserDetails的介紹在下面的小節具體講解,本節重要的內容是介紹SecurityContextHolder這個容器。
1.2 Authentication
先看看這個介面的原始碼長什麼樣:
package org.springframework.security.core;// <1>
public interface Authentication extends Principal, Serializable { // <1>
Collection<? extends GrantedAuthority> getAuthorities(); // <2>
Object getCredentials();// <2>
Object getDetails();// <2>
Object getPrincipal();// <2>
boolean isAuthenticated();// <2>
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
<1> Authentication是spring security包中的介面,直接繼承自Principal類,而Principal是位於java.security包中的。可以見得,Authentication在spring security中是最高級別的身份/認證的抽象。
<2> 由這個頂級介面,我們可以得到使用者擁有的許可權資訊列表,密碼,使用者細節資訊,使用者身份資訊,認證資訊。
還記得1.1節中,authentication.getPrincipal()返回了一個Object,我們將Principal強轉成了Spring Security中最常用的UserDetails,這在Spring Security中非常常見,介面返回Object,使用instanceof判斷型別,強轉成對應的具體實現類。介面詳細解讀如下:
- getAuthorities(),許可權資訊列表,預設是GrantedAuthority介面的一些實現類,通常是代表權限資訊的一系列字串。
- getCredentials(),密碼資訊,使用者輸入的密碼字串,在認證過後通常會被移除,用於保障安全。
- getDetails(),細節資訊,web應用中的實現介面通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值。
- getPrincipal(),敲黑板!!!最重要的身份資訊,大部分情況下返回的是UserDetails介面的實現類,也是框架中的常用介面之一。UserDetails介面將會在下面的小節重點介紹。
Spring Security是如何完成身份認證的?
1 使用者名稱和密碼被過濾器獲取到,封裝成Authentication,通常情況下是UsernamePasswordAuthenticationToken這個實現類。
2 AuthenticationManager 身份管理器負責驗證這個Authentication
3 認證成功後,AuthenticationManager身份管理器返回一個被填充滿了資訊的(包括上面提到的許可權資訊,身份資訊,細節資訊,但密碼通常會被移除)Authentication例項。
4 SecurityContextHolder安全上下文容器將第3步填充了資訊的Authentication,通過SecurityContextHolder.getContext().setAuthentication(…)方法,設定到其中。
這是一個抽象的認證流程,而整個過程中,如果不糾結於細節,其實只剩下一個AuthenticationManager 是我們沒有接觸過的了,這個身份管理器我們在後面的小節介紹。將上述的流程轉換成程式碼,便是如下的流程:
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
注意:上述這段程式碼只是為了讓大家瞭解Spring Security的工作流程而寫的,不是什麼原始碼。在實際使用中,整個流程會變得更加的複雜,但是基本思想,和上述程式碼如出一轍。
1.3 AuthenticationManager
初次接觸Spring Security的朋友相信會被AuthenticationManager,ProviderManager ,AuthenticationProvider …這麼多相似的Spring認證類搞得暈頭轉向,但只要稍微梳理一下就可以理解清楚它們的聯絡和設計者的用意。AuthenticationManager(介面)是認證相關的核心介面,也是發起認證的出發點,因為在實際需求中,我們可能會允許使用者使用使用者名稱+密碼登入,同時允許使用者使用郵箱+密碼,手機號碼+密碼登入,甚至,可能允許使用者使用指紋登入(還有這樣的操作?沒想到吧),所以說AuthenticationManager一般不直接認證,AuthenticationManager介面的常用實現類ProviderManager 內部會維護一個List<AuthenticationProvider>列表,存放多種認證方式,實際上這是委託者模式的應用(Delegate)。也就是說,核心的認證入口始終只有一個:AuthenticationManager,不同的認證方式:使用者名稱+密碼(UsernamePasswordAuthenticationToken),郵箱+密碼,手機號碼+密碼登入則對應了三個AuthenticationProvider。這樣一來四不四就好理解多了?熟悉shiro的朋友可以把AuthenticationProvider理解成Realm。在預設策略下,只需要通過一個AuthenticationProvider的認證,即可被認為是登入成功。
只保留了關鍵認證部分的ProviderManager原始碼:
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// 維護一個AuthenticationProvider列表
private List<AuthenticationProvider> providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
// 依次認證
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
catch (AuthenticationException e) {
lastException = e;
}
}
// 如果有Authentication資訊,則直接返回
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
//移除密碼
((CredentialsContainer) result).eraseCredentials();
}
//釋出登入成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
...
//執行到此,說明沒有認證成功,包裝異常資訊
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
}
ProviderManager 中的List,會依照次序去認證,認證成功則立即返回,若認證失敗則返回null,下一個AuthenticationProvider會繼續嘗試認證,如果所有認證器都無法認證成功,則ProviderManager 會丟擲一個ProviderNotFoundException異常。
到這裡,如果不糾結於AuthenticationProvider的實現細節以及安全相關的過濾器,認證相關的核心類其實都已經介紹完畢了:身份資訊的存放容器SecurityContextHolder,身份資訊的抽象Authentication,身份認證器AuthenticationManager及其認證流程。姑且在這裡做一個分隔線。下面來介紹下AuthenticationProvider介面的具體實現。
1.4 DaoAuthenticationProvider
AuthenticationProvider最最最常用的一個實現便是DaoAuthenticationProvider。顧名思義,Dao正是資料訪問層的縮寫,也暗示了這個身份認證器的實現思路。由於本文是一個Overview,姑且只給出其UML類圖:
按照我們最直觀的思路,怎麼去認證一個使用者呢?使用者前臺提交了使用者名稱和密碼,而資料庫中儲存了使用者名稱和密碼,認證便是負責比對同一個使用者名稱,提交的密碼和儲存的密碼是否相同便是了。在Spring Security中。提交的使用者名稱和密碼,被封裝成了UsernamePasswordAuthenticationToken,而根據使用者名稱載入使用者的任務則是交給了UserDetailsService,在DaoAuthenticationProvider中,對應的方法便是retrieveUser,雖然有兩個引數,但是retrieveUser只有第一個引數起主要作用,返回一個UserDetails。還需要完成UsernamePasswordAuthenticationToken和UserDetails密碼的比對,這便是交給additionalAuthenticationChecks方法完成的,如果這個void方法沒有拋異常,則認為比對成功。比對密碼的過程,用到了PasswordEncoder和SaltSource,密碼加密和鹽的概念相信不用我贅述了,它們為保障安全而設計,都是比較基礎的概念。
如果你已經被這些概念搞得暈頭轉向了,不妨這麼理解DaoAuthenticationProvider:它獲取使用者提交的使用者名稱和密碼,比對其正確性,如果正確,返回一個數據庫中的使用者資訊(假設使用者資訊被儲存在資料庫中)。
1.5 UserDetails與UserDetailsService
上面不斷提到了UserDetails這個介面,它代表了最詳細的使用者資訊,這個介面涵蓋了一些必要的使用者資訊欄位,具體的實現類對它進行了擴充套件。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
它和Authentication介面很類似,比如它們都擁有username,authorities,區分他們也是本文的重點內容之一。Authentication的getCredentials()與UserDetails中的getPassword()需要被區分對待,前者是使用者提交的密碼憑證,後者是使用者正確的密碼,認證器其實就是對這兩者的比對。Authentication中的getAuthorities()實際是由UserDetails的getAuthorities()傳遞而形成的。還記得Authentication介面中的getUserDetails()方法嗎?其中的UserDetails使用者詳細資訊便是經過了AuthenticationProvider之後被填充的。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsService和AuthenticationProvider兩者的職責常常被人們搞混,關於他們的問題在文件的FAQ和issues中屢見不鮮。記住一點即可,敲黑板!!!UserDetailsService只負責從特定的地方(通常是資料庫)載入使用者資訊,僅此而已,記住這一點,可以避免走很多彎路。UserDetailsService常見的實現類有JdbcDaoImpl,InMemoryUserDetailsManager,前者從資料庫載入使用者,後者從記憶體中載入使用者,也可以自己實現UserDetailsService,通常這更加靈活。
1.6 架構概覽圖
為了更加形象的理解上述我介紹的這些核心類,附上一張按照我的理解,所畫出Spring Security的一張非典型的UML圖
2 Spring Security Guides
2.1 引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
由於我們集成了springboot,所以不需要顯示的引入Spring Security文件中描述core,config依賴,只需要引入spring-boot-starter-security即可。
2.2 建立一個不受安全限制的web應用
這是一個首頁,不受安全限制
src/main/resources/templates/home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
這個簡單的頁面上包含了一個連結,跳轉到”/hello”。對應如下的頁面
src/main/resources/templates/hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
接下來配置Spring MVC,使得我們能夠訪問到頁面。
首先在application.yml配置檔案中配置thymeleaf模板的配置資訊
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
配置WebSecurityConfig 繼承WebSecurityConfigurerAdapter,配置自定義的登入頁面及成功返回頁面等。
@Configuration // 宣告為配置類
@EnableWebSecurity // 啟用 Spring Security web 安全的功能
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
/**
* 自定義配置
*/
@Override
protected void configure (HttpSecurity http) throws Exception {
http.authorizeRequests()//配置安全策略
.antMatchers("/", "/user/hello").permitAll()//定義/請求不需要驗證
.anyRequest().authenticated()//其餘的所有請求都需要驗證
.and()
.formLogin()
.loginPage("/login")//攔截後get請求跳轉的頁面
.defaultSuccessUrl("/hello")
.permitAll()
.and()
.logout()
.permitAll();
}
}
控制層:
@Controller
public class UserController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
@GetMapping(value = {"/","index"})
public String index(){
return "index";
}
@GetMapping(value = "login")
public String loginUI(){
return "login";
}
@RequestMapping(value = "aa",method = RequestMethod.GET)
public String aa(){
return "index";
}
@GetMapping(value = "admin")
public String admin(Model model){
model.addAttribute("title","標題");
model.addAttribute("content","內容");
model.addAttribute("extraInfo","你是admin");
return "admin";
}
}
注意:使用@Controller,而不能使用@RestController。因為@RestController會導致返回的結果為Json串資訊,而不是與前端模板封裝的.HTML檔案。
2.3 配置Spring Security
一個典型的安全配置如下所示:
@Configuration
@EnableWebSecurity <1>
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { <1>
@Override
protected void configure(HttpSecurity http) throws Exception {
http <2>
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth <3>
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
<1> @EnableWebSecurity註解使得SpringMVC集成了Spring Security的web安全支援。另外,WebSecurityConfig配置類同時集成了WebSecurityConfigurerAdapter,重寫了其中的特定方法,用於自定義Spring Security配置。整個Spring Security的工作量,其實都是集中在該配置類,不僅僅是這個guides,實際專案中也是如此。
<2> configure(HttpSecurity)定義了哪些URL路徑應該被攔截,如字面意思所描述:”/“, “/home”允許所有人訪問,”/login”作為登入入口,也被允許訪問,而剩下的”/hello”則需要登陸後才可以訪問。
<3> configureGlobal(AuthenticationManagerBuilder)在記憶體中配置一個使用者,admin/admin分別是使用者名稱和密碼,這個使用者擁有USER角色。
我們目前還沒有登入頁面,下面建立登入頁面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
這個Thymeleaf模板提供了一個用於提交使用者名稱和密碼的表單,其中name=”username”,name=”password”是預設的表單值,併發送到“/ login”。 在預設配置中,Spring Security提供了一個攔截該請求並驗證使用者的過濾器。 如果驗證失敗,該頁面將重定向到“/ login?error”,並顯示相應的錯誤訊息。 當用戶選擇登出,請求會被髮送到“/ login?logout”。
最後,我們為hello.html新增一些內容,用於展示使用者資訊。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
我們使用Spring Security之後,HttpServletRequest#getRemoteUser()可以用來獲取使用者名稱。 登出請求將被髮送到“/ logout”。 成功登出後,會將使用者重定向到“/ login?logout”。
2.4 新增啟動類
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
2.5 測試
訪問首頁http://localhost:8080/:
點選here,嘗試訪問受限的頁面:/hello,由於未登入,結果被強制跳轉到登入也/login:
輸入正確的使用者名稱和密碼之後,跳轉到之前想要訪問的/hello:
點選Sign out退出按鈕,訪問:/logout,回到登入頁面:
3 核心配置解讀
3.1 功能介紹
這是Spring Security入門指南中的配置項:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
當配置了上述的javaconfig之後,我們的應用便具備瞭如下的功能:
- 除了“/”,”/home”(首頁),”/login”(登入),”/logout”(登出),之外,其他路徑都需要認證。
- 指定“/login”該路徑為登入頁面,當未認證的使用者嘗試訪問任何受保護的資源時,都會跳轉到“/login”。
- 預設指定“/logout”為登出頁面
- 配置一個記憶體中的使用者認證器,使用admin/admin作為使用者名稱和密碼,具有USER角色
- 防止CSRF攻擊
- Session Fixation protection(可以參考我之前講解Spring Session的文章,防止別人篡改sessionId)
- Security Header(新增一系列和Header相關的控制)
- HTTP Strict Transport Security for secure requests
- 整合X-Content-Type-Options
- 快取控制
- 整合X-XSS-Protection.aspx)
- X-Frame-Options integration to help prevent Clickjacking(iframe被預設禁止使用)
- 為Servlet API集成了如下的幾個方法
- HttpServletRequest#getRemoteUser())
- HttpServletRequest.html#getUserPrincipal())
- HttpServletRequest.html#isUserInRole(java.lang.String))
- HttpServletRequest.html#login(java.lang.String, java.lang.String))
- HttpServletRequest.html#logout())
3.2 解讀@EnableWebSecurity
我們自己定義的配置類WebSecurityConfig加上了@EnableWebSecurity註解,同時繼承了WebSecurityConfigurerAdapter。你可能會在想誰的作用大一點,先給出結論:毫無疑問@EnableWebSecurity起到決定性的配置作用,他其實是個組合註解,背後SpringBoot做了非常多的配置。
---------------------------------------------------------------繼續研究----------------------------------------------------------------------------------------------
配置使用者認證邏輯,因為我們是有自己自定義的一套使用者體系的:
@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("使用者的使用者名稱: {}", username);
// TODO 根據使用者名稱,查詢到對應的密碼,與許可權
// 封裝使用者資訊,並返回。引數分別是:使用者名稱,密碼,使用者許可權
User user = new User(username, "123456",
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
return user;
}
}
這裡我們沒有進行過多的校驗,使用者名稱可以隨意的填寫,但是密碼必須得是“123456”,這樣才能登入成功。
同時可以看到,這裡User物件的第三個引數,它表示的是當前使用者的許可權,我們將它設定為”admin”。
UserDetails
剛剛我們在寫MyUserDetailsService
的時候,裡面實現了一個方法,並返回了一個UserDetails
。這個UserDetails 就是封裝了使用者資訊的物件,裡面包含了七個方法
public interface UserDetails extends Serializable {
// 封裝了許可權資訊
Collection<? extends GrantedAuthority> getAuthorities();
// 密碼資訊
String getPassword();
// 登入使用者名稱
String getUsername();
// 帳戶是否過期
boolean isAccountNonExpired();
// 帳戶是否被凍結
boolean isAccountNonLocked();
// 帳戶密碼是否過期,一般有的密碼要求性高的系統會使用到,比較每隔一段時間就要求使用者重置密碼
boolean isCredentialsNonExpired();
// 帳號是否可用
boolean isEnabled();
}
我們在返回UserDetails的實現類User的時候,可以通過User的構造方法,設定對應的引數。
執行一下程式進行測試,會發現登入介面有所改變
這是因為我們在配置檔案中配置了http.formLogin()
我們這裡隨便填寫一個User,然後Password寫填寫一個錯誤的(非123456)的。這時會提示校驗錯誤:
同時在控制檯,也會打印出剛才登入時填寫的user
現在我們再來使用正確的密碼進行登入試試,可以發現就會通過校驗,跳轉到正確的介面呼叫頁面了。
密碼加密解密
SpringSecurity中有一個PasswordEncoder
介面
public interface PasswordEncoder {
// 對密碼進行加密
String encode(CharSequence var1);
// 對密碼進行判斷匹配
boolean matches(CharSequence var1, String var2);
}
我們只需要自己實現這個介面,並在配置檔案中配置一下就可以了。
這裡我暫時以預設提供的一個實現類進行測試
// 放到WebSecurityConfig 自定義的Config類中 繼承WebSecurityConfigurerAdapter
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
加密使用:
@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("使用者的使用者名稱: {}", username);
String password = passwordEncoder.encode("123456");
logger.info("password: {}", password);
// 引數分別是:使用者名稱,密碼,使用者許可權
User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
return user;
}
}
這裡簡單的對123456進行了加密的處理。我們可以進行測試,發現每次打印出來的password都是不一樣的,這就是配置的BCryptPasswordEncoder所起到的作用。
自定義登入頁面
完成了登入頁面之後,就需要將它配置進行SpringSecurity
// WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 定義當需要使用者登入時候,轉到的登入頁面。
.loginPage("/login.html") // 設定登入頁面
.loginProcessingUrl("/user/login") // 自定義的登入介面
.and()
.authorizeRequests() // 定義哪些URL需要被保護、哪些不需要被保護
.antMatchers("/login.html").permitAll() // 設定所有人都可以訪問登入頁面
.anyRequest() // 任何請求,登入後可以訪問
.authenticated()
.and()
.csrf().disable(); // 關閉csrf防護
}
處理不同型別的請求
因為現在一般都前後端分離了,後端提供介面供前端呼叫,返回JSON格式的資料給前端。剛才那樣,呼叫了被保護的介面,直接進行了頁面的跳轉,在web端還可以接受,但是在App端就不行了, 所以我們還需要做進一步的處理。
這裡做一下簡單的思路整理
首先來寫自定義的Controller,當需要身份認證的時候就跳轉過來
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(getClass());
// 原請求資訊的快取及恢復
private RequestCache requestCache = new HttpSessionRequestCache();
// 用於重定向
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 當需要身份認證的時候,跳轉過來
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public BaseResponse requireAuthenication(HttpServletRequest request, HttpServletResponse response) throws IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引發跳轉的請求是:" + targetUrl);
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response, "/login.html");
}
}
return new BaseResponse("訪問的服務需要身份認證,請引導使用者到登入頁");
}
}
當然還需要將配置檔案進行相應的修改, 這裡我就不貼程式碼了。 就是將該介面開放出來 。
擴充套件:
這裡我們是寫死了如果是從網頁訪問的介面,那麼就跳轉到”/login.html”頁面,其實我們可以擴充套件一下,將該跳轉地址配置到配置檔案中,這樣會更方便的。
自定義處理登入成功/失敗
在之前的測試中,登入成功了都是進行了頁面的跳轉。
在前後端分離的情況下,我們登入成功了可能需要向前端返回使用者的個人資訊,而不是直接進行跳轉。登入失敗也是同樣的道理。
這裡涉及到了Spring Security中的兩個介面AuthenticationSuccessHandler
和AuthenticationFailureHandler
。我們可以實現這個介面,並進行相應的配置就可以了。 當然框架是有預設的實現類的,我們可以繼承這個實現類再來自定義自己的業務
@Component("myAuthenctiationSuccessHandler")
public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.info("登入成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
這裡我們通過response返回一個JSON字串回去。
這個方法中的第三個引數Authentication
,它裡面包含了登入後的使用者資訊(UserDetails),Session的資訊,登入資訊等。
@Component("myAuthenctiationFailureHandler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
logger.info("登入失敗");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new BaseResponse(exception.getMessage())));
}
}
這個方法中的第三個引數AuthenticationException
,包括了登入失敗的資訊。
同樣的,還是需要在配置檔案中進行配置,這裡就不貼出全部的程式碼了,只貼出相應的語句
//在WebSecurityConfig自定義配置檔案中加上如下配置介面資訊
.successHandler(myAuthenticationSuccessHandler) // 自定義登入成功處理
.failureHandler(myAuthenticationFailureHandler) // 自定義登入失敗處理
記住我功能的基本原理
“記住我”功能SpringSecurity原始碼分析
根據最開始的原理分析,我們可以知道一般是在認證成功之後,才會去做記住我的操作,所以我們可以看successfulAuthentication
方法中的相關程式碼
// AbstractAuthenticationProcessingFilter.java
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
// 呼叫rememberMeServices,進行登入操作
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
這裡在認證成功之後,呼叫了RememberMeServices
的loginSuccess
方法,該方法會進一步呼叫onLoginSuccess
:
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username= successfulAuthentication.getName();
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
// 進行TokenRepository操作
this.tokenRepository.createNewToken(persistentToken);
// 進行Cookie操作
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
這樣相關的資料庫操作和Token操作就完成了。
接下來看看下次登入的時候,是怎麼進行記住我認證的。這裡我們就直接看RememberMeAuthenticationFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// 1.先判斷之前是否已經通過認證了
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 2.通過RememberMeServices進行登入
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
if (rememberMeAuth != null) {
try {
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
this.onSuccessfulAuthentication(request, response, rememberMeAuth);
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
}
if (this.successHandler != null) {
this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
return;
}
} catch (AuthenticationException var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
}
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var8);
}
}
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
}
------------------------------------------------------------再次繼續研究--------------------------------------------------------------------------
1完整POM檔案
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lc</groupId>
<artifactId>springboot-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-security</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!---thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.4</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2完整logback.xml檔案
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日誌級別從低到高分為TRACE < DEBUG < INFO < WARN < ERROR < FATAL,
如果設定為WARN,則低於WARN的資訊都不會輸出 -->
<!-- scan:當此屬性設定為true時,配置文件如果發生改變,將會被重新載入,預設值為true -->
<!-- scanPeriod:設定監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,預設單位是毫秒。
當scan為true時,此屬性生效。預設的時間間隔為1分鐘。 -->
<!-- debug:當此屬性設定為true時,將打印出logback內部日誌資訊,實時檢視logback執行狀態。預設值為false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback</contextName>
<!-- name的值是變數的名稱,value的值時變數定義的值。通過定義的值會被插入到logger上下文中。定義後,可以使“${}”來使用變數。 -->
<property name="LOG_HOME" value="./logs" />
<!--0. 日誌格式和顏色渲染 -->
<!-- 彩色日誌依賴的渲染類 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日誌格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--1. 輸出到控制檯-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日誌appender是為開發使用,只配置最底級別,控制檯輸出的日誌級別是大於或等於此級別的日誌資訊-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 設定字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--2. 輸出到文件-->
<!-- 2.1 level為 DEBUG 日誌,時間滾動輸出 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日誌文件的路徑及文件名 -->
<file>${LOG_HOME}/web_debug.log</file>
<!--日誌文件輸出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 設定字符集 -->
</encoder>
<!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日誌歸檔 -->
<fileNamePattern>${LOG_HOME}/debug.log.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日誌文件保留天數-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日誌文件只記錄debug級別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.2 level為 INFO 日誌,時間滾動輸出 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日誌文件的路徑及文件名 -->
<file>${LOG_HOME}/web_info.log</file>
<!--日誌文件輸出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日誌歸檔路徑以及格式 -->
<fileNamePattern>${LOG_HOME}/info.log.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日誌文件保留天數-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日誌文件只記錄info級別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.3 level為 WARN 日誌,時間滾動輸出 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日誌文件的路徑及文件名 -->
<file>${LOG_HOME}/web_warn.log</file>
<!--日誌文件輸出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此處設定字符集 -->
</encoder>
<!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/warn.log.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日誌文件保留天數-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日誌文件只記錄warn級別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.4 level為 ERROR 日誌,時間滾動輸出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日誌文件的路徑及文件名 -->
<file>${LOG_HOME}/web_error.log</file>
<!--日誌文件輸出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此處設定字符集 -->
</encoder>
<!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.log.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日誌文件保留天數-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日誌文件只記錄ERROR級別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用來設定某一個包或者具體的某一個類的日誌列印級別、
以及指定<appender>。<logger>僅有一個name屬性,
一個可選的level和一個可選的addtivity屬性。
name:用來指定受此logger約束的某一個包或者具體的某一個類。
level:用來設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
還有一個特俗值INHERITED或者同義詞NULL,代表強制執行上級的級別。
如果未設定此屬性,那麼當前logger將會繼承上級的級別。
addtivity:是否向上級logger傳遞列印資訊。預設是true。
<logger name="org.springframework.web" level="info"/>
<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-->
<!--
使用mybatis的時候,sql語句是debug下才會列印,而這裡我們只配置了info,所以想要檢視sql語句的話,有以下兩種操作:
第一種把<root level="info">改成<root level="DEBUG">這樣就會列印sql,不過這樣日誌那邊會出現很多其他訊息
第二種就是單獨給dao下目錄配置debug模式,程式碼如下,這樣配置sql語句會列印,其他還是正常info級別:
【logging.level.org.mybatis=debug logging.level.dao=debug】
-->
<!--
root節點是必選節點,用來指定最基礎的日誌輸出級別,只有一個level屬性
level:用來設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能設定為INHERITED或者同義詞NULL。預設是DEBUG
可以包含零個或多個元素,標識這個appender將會新增到這個logger。
-->
<!-- 4. 最終的策略 -->
<!-- 4.1 開發環境:列印控制檯-->
<!--<springProfile name="dev">
<logger name="com.sdcm.pmp" level="debug"/>
</springProfile>-->
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!-- 4.2 生產環境:輸出到文件
<springProfile name="pro">
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
<appender-ref ref="WARN_FILE" />
</root>
</springProfile> -->
</configuration>
3,完整application.yml檔案
spring:
datasource:
###################以下為druid增加的配置###########################
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8&useSSL=false
username: test
password: 1
driverClassName: com.mysql.jdbc.Driver
# 下面為連線池的補充設定,應用到上面所有資料來源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置獲取連線等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 開啟PSCache,並且指定每個連線上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆
filters: stat,wall,log4j
# 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合併多個DruidDataSource的監控資料
useGlobalDataSourceStat: true
###############以上為配置druid新增的配置########################################
# thymeleaf
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
jpa:
properties:
hibernate:
database: MySQL
show_sql: true
format_sql: true
database-platform: org.hibernate.dialect.MySQL5Dialect
mybatis:
#mybatis的mapper檔案所在路徑
mapper-locations: classpath:mapper/*.xml
#實體類路徑
type-aliases-package: com.lc.entity
logging:
level:
org.springframework.security: INFO