1. Spring Security 簡介
Spring Security 是一種基于 Spring AOP 和 Servlet 過濾器的安全架構。原名Acegi Security,最早于2003年起源于Spring社群。 2007年末正式歸為Spring Framework的正式子項目,并改名為Spring Security 。此後, Spring Security有了長遠發展,現已成為一款基于Spring Framework的廣泛應用的安全架構,主要為應用服務提供使用者認證(Authentication)和使用者授權(Authorization)功能。詳情可參考Spring Security 官方文檔。
Spring Security 針對安全方面的兩大難題, 鑒權(Authentication)和授權(Authorization)提供了靈活強大的解決方案。
- 使用者鑒權(Authentication), 是指對使用者身份的鑒權, 驗證某個使用者是否為系統中的合法對象, 是否能夠通路對應的系統資源。比如使用者輸入賬戶和密碼登陸系統。
- 使用者授權(Authorization),是指授予通過認證的使用者指定的系統資源操作權限, 能否執行具體某個操作。比如使用者能夠通路操作的菜單,能夠請求的功能接口, 這些都是系統資源。
Spring Security優勢:
- 靈活性, Spring Security并不局限于Spring MVC,雖然它是基于Spring Framework實作的,但它并不依賴于Spring MVC,可以獨立于MVC應用在其他Java EE架構之上。
- 功能強大,Spring Security的安全管制并不隻限制于Web請求,除此之外它還可以針對方法調用通過AOP的方式進行安全管制,甚至可以對域對象執行個體(Domain Object Instance)進行通路控制。
- 安全保護, 防止僞造身份, Spring Security 會自動攔截站點所有狀态變化的請求(非GET,HEAD,OPTIONS和TRACE的請求),防止跨站請求僞造(CSRF防護),即防止其他網站或是程式POST等請求本站點。
如果項目需要安全控制功能,不用自己去實作一套, 內建Spring Security專業安全架構是首選, 适用背景管理、接口資源管理、微服務統一鑒權等場景。
2. Spring Security設計處理機制
處理流程圖:
- 用戶端發起一個請求,進入 Security 過濾器鍊。
- 當到 LogoutFilter 的時候判斷是否是登出路徑,如果是登出路徑則到 logoutHandler ,如果登出成功則到 logoutSuccessHandler 登出成功處理,如果登出失敗則由 ExceptionTranslationFilter ;如果不是登出路徑則直接進入下一個過濾器。
- 當到 UsernamePasswordAuthenticationFilter 的時候判斷是否為登入路徑,如果是,則進入該過濾器進行登入操作,如果登入失敗則到 AuthenticationFailureHandler 登入失敗處理器處理,如果登入成功則到 AuthenticationSuccessHandler 登入成功處理器處理,如果不是登入請求則不進入該過濾器。
- 當到 FilterSecurityInterceptor 的時候會拿到 uri ,根據 uri 去找對應的鑒權管理器,鑒權管理器做鑒權工作,鑒權成功則到 Controller 層否則到 AccessDeniedHandler 鑒權失敗處理器處理。
- 投票機制, 三種表決方式, 預設采用一票制(AffirmativeBased)
類名 | 描述 |
AffirmativeBased | 隻要有一個投票器允許通路, 請求立刻允許放行, 不管之前存在拒絕的決定 |
ConsensusBased | 多數票機制(允許或拒絕),多數的一方決定了AccessDecisionManager的結果。平局的投票和空票(全部棄權)的結果是可配置的 |
UnanimousBased | 所有的投票器必須全部是允許的, 否則通路将被拒絕 |
3. Spring Boot 與Spring Security 內建配置
Spring Boot 與Spring Security 內建, 包含一般內建用法, 還包括自定義使用者登陸頁面使用, 自定義記憶體模式驗證, 以及自定義登陸成功與失敗邏輯處理。
1、建立工程spring-boot-security-integrate
啟動類:
com.mirson.spring.boot.security.integrate.startup.SecurityIntegrateApplication
@SpringBootApplication
@ComponentScan(basePackages = {"com.mirson"})
public class SecurityIntegrateApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityIntegrateApplication.class, args);
}
}
2、MAVEN依賴
<dependencies>
<!-- Spring Boot Security 安全依賴元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Thymeleaf 模闆依賴元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Boot Web 依賴元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring boot freemarker 自動化配置元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
3、定義一個外部通路接口
1)建立實體
定義一個使用者實體
com.mirson.spring.boot.security.integrate.po.User
@Data
public class User {
/**
* ID
*/
private Integer id;
/**
* 使用者名稱
*/
private String name;
/**
* 年齡
*/
private String age;
/**
* 省份
*/
private String province;
/**
* 建立時間
*/
private Date createDate;
}
2)提供Web通路接口
提供一個擷取使用者資訊接口
com.mirson.spring.boot.security.integrate.controller.UserController
@RestController
@RequestMapping("/user")
@Log4j2
public class UserController {
@GetMapping("/getUserInfo")
@ResponseBody
public User getUserInfo() {
User user = new User();
user.setId(0);
user.setAge("21");
user.setName("user1");
user.setCreateDate(new Date());
return user;
}
}
4、工程配置
server:
port: 22618
spring:
application:
name: security-integrate
# security 安全配置
security:
user:
name: "admin"
password: "admin"
設定預設的使用者名與密碼為admin。
5、功能驗證
1) 請求擷取使用者資訊接口
通路接口: http://127.0.0.1:22618/getUserInfo
沒有鑒權的情況下, 會出現登陸界面。
2)輸入使用者資訊admin/admin, 再次請求擷取使用者資訊接口
正确輸入使用者資訊後, 可以正常通路使用者資訊接口。
4. Spring Security 自定義鑒權實作
4.1 自定義登陸頁面處理
Spring Security 内置會有一套登陸頁面, 也可以自定修改, 這裡通過freemark模闆來實作自定登陸頁面渲染。
application.yml增加配置:
# freemarker 模闆配置
freemarker:
allow-request-override: false
allow-session-override: false
cache: true
charset: UTF-8
check-template-location: true
content-type: text/html
enabled: true
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: true
prefer-file-system-access: true
suffix: .ftl
template-loader-path: classpath:/templates/
增加freemark模闆檔案:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>自定義系統登陸</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body>
<div class="container form-margin-top">
<form class="form-signin" action="/user/doUserLogin" method="post">
<h2 class="form-signin-heading" align="center">自定義系統登陸</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="賬号" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密碼" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
</form>
</div>
<footer>
<p>support by: mirson</p>
</footer>
</body>
</html>
添加CSS靜态資源檔案
添加JAVA CONFIG配置:
com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated()
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登入頁面
.permitAll() //允許所有人通路該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法送出
.httpBasic();
}
}
重新開機, 通路接口: http://127.0.0.1:22618/user/getUserInfo
會自動跳轉到自定義的登陸頁面:
4.2 自定義資源通路配置
修改com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration配置:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登入頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
.permitAll() //允許所有人通路該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法送出
.httpBasic();
}
}
通過Spring Security 可以控制, 哪些資源需要受權限保護, 哪些可以開放通路。
- 開放通路
/user/userLoginForm 使用者登陸頁面
/user/doUserLogin 登陸處理位址
/css/** 靜态資源檔案
- 權限保護
/user/getUserInfo 擷取使用者資訊接口
可以指定Role角色權限, 不指定, 隻要登陸即擁有通路權限。
loginPage指定/user/userLoginForm為自定義登陸頁面;
loginProcessingUrl為登陸請求處理接口位址,可以不用對其做具體實作,Spring Security 會做預設處理。
4.3 自定義記憶體模式鑒權
1、建立鑒權使用者對象:
com.mirson.spring.boot.security.integrate.po.OAuthUser
@Data
public class OAuthUser extends User {
public OAuthUser(String account, String password){
super(account, password, true, true, true, true, Collections.EMPTY_SET);
}
}
繼承的是Spring Security 的User對象, 将賬号和密碼資訊, 傳遞至父類構造方法。
2、修改SpringSecurityConfiguration配置
增加:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 使用者認證服務
* */
@Bean
@Override
protected UserDetailsService userDetailsService() {
//建立基于記憶體使用者管理對象
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//自定義權限
Collection<GrantedAuthority> adminAuth = new ArrayList<>();
adminAuth.add(new SimpleGrantedAuthority("ADMIN"));
//自定義使用者
OAuthUser oAuthUser = new OAuthUser("admin", "admin123");
manager.createUser(oAuthUser);
return manager;
}
/**
* 配置密碼編碼器, 不需加密
* @return
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
...
}
采用記憶體模式管理使用者對象InMemoryUserDetailsManager, 自定義認證使用者名和密碼, 分别為admin,admin123。 需要配置密碼編碼器, 可以支援自定義密碼加密方式, 這裡不需加密, 配置NoOpPasswordEncoder。
4.4 自定義登陸成功處理器
Spring Security 提供了接口, 登陸成功, 可以通過處理器實作自定義邏輯。 建立com.mirson.spring.boot.security.integrate.handler.SecuritySuccessHandler
@Component
@Log4j2
public class SecuritySuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
/**
* 認證成功處理
* @param request
* @param response
* @param authentication
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("Process in SecuritySuccessHandler ==> login success.");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
這裡通過自定義登陸成功處理器, 将登陸成功的資訊傳回用戶端。
将處理器加入到自定義配置SpringSecurityConfiguration中:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登入頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
.successHandler(securitySuccessHandler) // 自定義登陸成功處理器
.permitAll() //允許所有人通路該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法送出
.httpBasic();
}
4.5 自定義登陸失敗處理器
如果登陸失敗, 也可以通過處理器實作自定義邏輯。 建立com.mirson.spring.boot.security.integrate.handler.SecurityFailureHandler
@Component
@Log4j2
public class SecurityFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.info("Process in SecurityFailureHandler ==> login failure.");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
}
}
将登陸失敗的錯誤資訊, 傳回給用戶端。
修改自定義配置SpringSecurityConfiguration:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登入頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理位址
.successHandler(securitySuccessHandler) // 自定義登陸成功處理器
.failureHandler(securityFailureHandler) // 自定義登陸失敗處理器
.permitAll() //允許所有人通路該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法送出
.httpBasic();
}
4.6 自定義鑒權功能驗證
1、驗證記憶體模式鑒權
記憶體模式我們設定的使用者名和密碼為admin/admin123
預設配置檔案是admin/admin
輸入admin/admin123, 成功登陸, 記憶體模式已生效。
2、登陸成功處理器驗證
重新開機服務, 通路擷取使用者資訊接口, http://127.0.0.1:22618/user/getUserInfo
預設是會跳轉到登陸頁面, 如果沒配置登陸成功處理器, 登陸成功後, 會進入上一次通路頁面
可以看到, 登陸成功後, 并沒有跳轉到上一次通路的使用者資訊接口, 而是傳回了登陸成功處理器的結果。
3、登陸失敗處理器驗證
同樣, 問擷取使用者資訊接口, http://127.0.0.1:22618/user/getUserInfo, 會自動跳轉到登陸頁面。
采用錯誤的使用者密碼, 傳回了登陸失敗處理器的結果。
5. 總結
Spring Security 提供了鑒權與授權的功能支援, 這裡做了詳細講解, 如何使用與配置, 并講解了自定義鑒權處理功能, 實際業務當中,并非一層不變, 會做不同配置修改,比如自定義資源通路配置, 不同項目有不同的要求, 掌握這些自定義配置, 基本可以覆寫主要的業務場景, 針對更複雜的鑒權, 可以采用oauth2做鑒權處理, 在後續教程中會做講解。
教程源碼下載下傳位址: https://download.csdn.net/download/hxx688/86400104