本文記錄在SpringBoot使用SpringSecurity進行安全訪問控制。
一 什么是Security
Spring Security是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。
目前在我們項目中使用的是RBAC基於角色的權限訪問控制(Role-Based Access Control),用戶與角色關聯,角色與權限相關聯,用戶通過角色間接的得到權限。關系如下圖
用戶:權限的擁有者
角色:一些權限的集合
權限:操作的對象或資源
用戶擁有某種角色,從而擁有了對資源的訪問操作權限,在訪問時SpringSecurity會對所有請求進行攔截,有權限的請求放行,否則攔截。
二 SpringBoot使用SpringSecurity
SpringBoot對SpringSecurity做了支持,要使用的話很方便,只需要引入相應的依賴(spring-boot-starter-security)就可以了。
示例代碼主要完成以下功能:
1 系統的首頁和登錄頁面及一些靜態資源(CSS,JS),默認所有用戶都可以訪問;
2 除了第一步的,其他的所有資源路徑訪問均需要用戶通過認證;
3 登錄用戶在頁面只能看到擁有的角色所對應的權限(資源或操作);
修改pom.xml文件,添加依賴
<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>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
創建配置類,繼承 WebSecurityConfigurerAdapter,重寫一些配置方法
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/static/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login").permitAll()
.successForwardUrl("/main")
.failureUrl("/login")
.and().logout()
.logoutUrl("/logout").permitAll()
.logoutSuccessUrl("/login");
}
}
@EnableWebSecurity 用來說明開啟安全認證
configure(HttpSecurity http) 配置相關訪問操作的策略
.antMatchers("/", "/static/**").permitAll() 說明項目根路徑/ 及static路徑下的靜態資源可以被匿名訪問
.anyRequest().authenticated() 說明除了可以被匿名訪問的資源外,其他所有資源的訪問都要經過認證
.formLogin() 說明使用用戶自定義的登錄,如果不配置的話,會使用SpringSecurity默認提供的登錄頁面,/login 資源可以被匿名訪問,登錄成功后訪問/main,失敗后訪問/login
.logout() 退出功能,SpringSecurity默認對/logout做了監控
用戶登錄就是對當前用戶的身份信息做認證,我們需要對相應的方法做重寫
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
指定使用自定義的實現用戶認證及授權的userDetailsService和密碼的加密器
密碼加密器
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
認證與授權
@Bean
@Override
protected UserDetailsService userDetailsService() {
return new UserDetailsService(){
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//此處模擬數據庫查詢用戶
User user = new User();
user.setUserId(2);
user.setUsername(username);
user.setPassword("$2a$10$GS71hBKk0MaykCWZC/eo2e7Y0Z77zDNCYE06xxAmW37gl850E6I4G");
user.setTelephone("13000000000");
user.setEmail("13000000000@qq.com");
if(user == null) throw new UsernameNotFoundException("User name:"+username+" not fount");
SecurityUser securityUser= new SecurityUser(user);
return securityUser;
}
};
}
/**
* 真正用於登錄驗證的安全用戶(UserDetails)
*/
class SecurityUser extends User implements UserDetails {
/**
* 用戶權限
*/
private Set<SimpleGrantedAuthority> permissions;
public Set<SimpleGrantedAuthority> getPermissions() {
return permissions;
}
public void setPermissions(Set<SimpleGrantedAuthority> permissions) {
this.permissions = permissions;
}
public SecurityUser(User user){
if(user != null){
this.setUserId(user.getUserId());
this.setUsername(user.getUsername());
this.setPassword(user.getPassword());
this.setEmail(user.getEmail());
this.setTelephone(user.getTelephone());
Set<SimpleGrantedAuthority> gasSet = (Set<SimpleGrantedAuthority>) getAuthorities();
if(gasSet.size()>0){
this.setPermissions(gasSet);
}
}
}
/**
* 獲取用戶權限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//要返回的用戶權限集合
Set<GrantedAuthority> permsSet = new HashSet<GrantedAuthority>();
//模擬數據庫查詢用戶所擁有的角色對應的權限
permsSet.add(new SimpleGrantedAuthority("/user/add"));
permsSet.add(new SimpleGrantedAuthority("/user/edit"));
permsSet.add(new SimpleGrantedAuthority("/user/delete"));
permsSet.add(new SimpleGrantedAuthority("/user/list"));
//區分不同用戶擁有不同權限,admin用戶加權限
if (this.getUsername().equals("admin")) {
permsSet.add(new SimpleGrantedAuthority("/role/list"));
permsSet.add(new SimpleGrantedAuthority("/role/add"));
permsSet.add(new SimpleGrantedAuthority("/role/edit"));
permsSet.add(new SimpleGrantedAuthority("/role/delete"));
}
return permsSet;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
控制器
@Controller
public class LoginController {
/**
* 訪問根路徑時跳轉到index頁面
* @return
*/
@GetMapping("/")
public String root(){
return "index";
}
/**
* 跳轉到登錄頁面
* @return
*/
@GetMapping("/login")
public String login(){
return "login";
}
/**
* 登錄成功后訪問
* @return
*/
@PostMapping("/main")
public String main(){
return "main";
}
}
index頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
this is index page<br/>
<a th:href="@{/user/login}">登錄</a>
</body>
</html>
登錄頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
this is login page
<form th:action="@{/login}" method="post">
<input type="text" th:id="username" th:name="username" value="" >
<input type="password" th:id="password" th:name="password" value="">
<input type="submit" th:value="提交" >
</form>
</body>
</html>
main頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="這是主頁面">text</h1>
<form th:action="@{/logout}" method="post"><button th:type="submit" th:text="退出">text</button></form>
<hr/>
<th:block sec:authorize="hasAuthority('/user/add')">
<a th:href="@{/user/add}">添加用戶</a>
</th:block>
<th:block sec:authorize="hasAuthority('/user/edit')">
<a th:href="@{/user/edit}">修改用戶</a>
</th:block>
<th:block sec:authorize="hasAuthority('/user/delete')">
<a th:href="@{/user/delete}">刪除用戶</a>
</th:block>
<th:block sec:authorize="hasAuthority('/user/list')">
<a th:href="@{/user/list}">查詢用戶</a>
</th:block>
<th:block sec:authorize="hasAuthority('/role/add')">
<a th:href="@{/role/add}">添加角色</a>
</th:block>
<th:block sec:authorize="hasAuthority('/role/delete')">
<a th:href="@{/role/delete}">刪除角色</a>
</th:block>
<th:block sec:authorize="hasAuthority('/role/edit')">
<a th:href="@{/role/edit}">修改角色</a>
</th:block>
<th:block sec:authorize="hasAuthority('/role/list')">
<a th:href="@{/role/list}">查詢角色</a>
</th:block>
</body>
</html>
sec:authorize="hasAuthority('')" 說明當用戶擁有此權限的時候,操作對用戶可見,否則不可見
分別用user用戶和admin登錄后看到首頁信息