Spring Security 安全框架
阿新 • • 發佈:2021-07-21
目錄
一、Spring Security 安全框架
1.介紹
Spring Security框架為我們提供了使用者認證和授權的能力。
- 使用者認證: 登陸驗證
- 使用者授權:某個使用者是否具有某個許可權。具有相應許可權的使用者才能進行具體的操作。比如: 管理員能進行工資設定,但是普通使用者只能檢視工資。
2.初體驗
1) 引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2)設計介面
在訪問介面時就會自動的被security的登陸頁面攔截
使用者名稱: user
密碼: 控制檯會提示的
3.過濾器鏈
security實際上就是一串過濾器鏈。通過過濾器鏈來實現使用者驗證和授權。
4.多種方式設定使用者名稱和密碼
方式一:yml配置檔案
# 設定使用者名稱和密碼
spring:
security:
user:
name: qfadmin
password: 123456
方式二: 設定配置類的方式
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123456"); auth.inMemoryAuthentication().withUser("xiaoming").password(password).roles(); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
方式三:設定自定義登陸頁面
步驟:
- 建立了一個UserDetailsService介面的實現類
/**
* 建立一個UserDetailsService實現類來設定使用者名稱和密碼
*/
@Service("userDetailsService")
public class MyUserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//設定角色,角色的概念在之後章節介紹
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
//模擬從資料庫獲取使用者名稱和密碼
User qfadmin = new User("qfadmin", passwordEncoder.encode("123456"), auths);
return qfadmin;
}
}
- 編寫配置類
@Configuration
public class SecurityConfigPro extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //設定自定義登陸頁面
.loginProcessingUrl("/usr/login") //登陸時訪問的路徑
.defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
.and().authorizeRequests()
.antMatchers("/","/hello","/user/login").permitAll() //設定可以直接訪問的路徑,取消攔截
.anyRequest().authenticated()
.and().csrf().disable(); //關閉csrf防護
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 設計自定義登陸頁面
注意: 表單中的使用者名稱和密碼的name屬性必須是: username、password
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->
<title>Bootstrap 101 Template</title>
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支援 HTML5 元素和媒體查詢(media queries)功能 -->
<!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<form class="form-horizontal" action="/usr/login" method="post">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="text" name="username" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> Remember me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
<!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只加載單個外掛。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
</body>
</html>
5.角色和許可權的四個方法
1)角色和許可權的概念
某個角色,擁有怎樣的許可權。
比如說管理員角色,擁有檔案管理、日誌管理的許可權
比如說 普通使用者角色,擁有檔案檢視的許可權。
對於角色和許可權來說,必須得看使用者屬於哪個角色,於是具有相應的許可權。
比如小明屬於普通使用者,那麼小明只有檢視檔案的許可權。
2)hasAuthority、hasAnyAuthority、hasRole、hasAnyRole
判斷當前登陸成功的使用者,能否具有訪問指定路徑的許可權的四個方法:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //設定自定義登陸頁面
.loginProcessingUrl("/usr/login") //登陸時訪問的路徑
.defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
.and().authorizeRequests()
.antMatchers("/","/hello","/usr/login").permitAll() //設定可以直接訪問的路徑,取消攔截
//1.hasAuthority方法:當前登陸使用者,只有具有admin許可權才可以訪問這個路徑
// .antMatchers("/index").hasAuthority("admins")
//2.hasAnyAuthority方法:當前登陸使用者,具有指定的多個許可權中的某一個許可權即可訪問
//.antMatchers("/index").hasAnyAuthority("admins","admin")
//3.hasRole:當前登陸使用者是否屬於指定的角色
// .antMatchers("/index").hasRole("student")
//4.hasAnyRole: 當前登陸使用者滿足某一個角色即可
.antMatchers("/index").hasAnyRole("student","admin")
.anyRequest().authenticated()
.and().csrf().disable(); //關閉csrf防護
}
還需要修改,UserDetailsService中的auths集合,例子中直接寫死了許可權和角色,實際可以從資料庫獲取並返回。
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//設定角色,角色的概念在之後章節介紹
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role,admin,ROLE_student");
//模擬從資料庫獲取使用者名稱和密碼,且當前使用者有admin的許可權
User qfadmin = new User("qfadmin", passwordEncoder.encode("123456"), auths);
return qfadmin;
}
6.security中的常用註解
- Secured: 具有指定的角色許可權才能訪問註解的方法
@RequestMapping("/items")
@Secured({"ROLE_student1"})
@ResponseBody
public String items(){
return "items";
}
- PreAuthorize: 在訪問方法之前先進行角色和許可權的驗證
@RequestMapping("/items")
@PreAuthorize("hasAnyAuthority('admin')")
@ResponseBody
public String items(){
return "items";
}
- PostAuthorize: 先執行方法,再進行角色和許可權的驗證
@RequestMapping("/postItems")
@PostAuthorize("hasAnyAuthority('teacher')")
@ResponseBody
public String postItems(){
//先執行方法內容,再做許可權校驗
System.out.println("show detail here...");
return "show post items";
}
- PostFilter:返回到前端的資料會被過濾,過濾條件在註解中宣告
@RequestMapping("/users")
@PreAuthorize("hasAnyAuthority('admin')")
@PostFilter("filterObject.name=='xiaoming'")
@ResponseBody
public List<User> users(){
List<User> users = new ArrayList<>();
users.add(new User(1001L,"xiaoming"));
users.add(new User(1002L,"xiaowang"));
return users;
}
- PreFilter:只有符合條件的資料才會被傳入方法,條件在註解中宣告
@RequestMapping("/preFilterItems")
@PreAuthorize("hasAnyAuthority('admin')")
@PreFilter(value="filterObject.userName == 'xiaoming'")
public List<User> getUsersByPreFilter(@RequestBody List<User> list){
//只有userName是'xiaoming'的資料才會被傳入
list.forEach(t->{
System.out.println(t.getUserName());
});
return list;
}
關於如何校驗資料庫儲存的密碼。
@Test
public void testPassword(){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//1.資料庫中要存密碼的密文
String password = "abc123";//明文
String encodePassword = passwordEncoder.encode(password);
//2.使用者輸入一個明文,怎麼和資料庫的密文進行比較
boolean matches = passwordEncoder.matches(password, encodePassword);
System.out.println(matches);
}
二、實現登出功能
1.登出的流程
2.步驟
1)在配置類中添加註銷配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置沒有許可權的跳轉頁面
http.exceptionHandling().accessDeniedPage("/error.html");
//登出的配置
http.logout().logoutUrl("/logout") //登出時訪問的路徑
.logoutSuccessUrl("/logoutSuccess").permitAll(); //登出成功後訪問的路徑
http.formLogin()
.loginPage("/login.html") //設定自定義登陸頁面
.loginProcessingUrl("/usr/login") //登陸時訪問的路徑
.defaultSuccessUrl("/index").permitAll() //登陸成功後跳轉的路徑
.and().authorizeRequests()
.antMatchers("/","/hello","/user/login").permitAll() //設定可以直接訪問的路徑,取消攔截
//1.hasAuthority方法:當前登陸使用者,只有具有admin許可權才可以訪問這個路徑
// .antMatchers("/index").hasAuthority("admins")
//2.hasAnyAuthority方法:當前登陸使用者,具有指定的多個許可權中的某一個許可權即可訪問
//.antMatchers("/index").hasAnyAuthority("admins","admin")
//3.hasRole:當前登陸使用者是否屬於指定的角色
// .antMatchers("/index").hasRole("student")
//4.hasAnyRole: 當前登陸使用者滿足某一個角色即可
// .antMatchers("/index").hasAnyRole("student","admin")
.anyRequest().authenticated()
.and().csrf().disable(); //關閉csrf防護
}
2)建立登出連結,連結中訪問登出路徑/logout
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
歡迎來到 千鋒杭州Java2007大家庭!<a href="/logout">登出</a>
</body>
</html>
3)編寫登出成功後的跳轉介面
@RequestMapping("/logoutSuccess")
public String logoutSuccess(){
return "logoutsuccess";
}