前面我們學習了 spring security 與 springmvc 的整合入門教程。
這一節我們來學習一下 spring security 與 springboot 整合,為了力求簡單,此處不演示資料庫相關操作。
快速開始pom.xml引入核心的 spring-boot-starter-security 依賴
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
整體目錄結構如下:
│ Application.java
│
├─config
│ MyPasswordEncoder.java
│ WebSecurityConfig.java
│
├─controller
│ AuthController.java
│
├─model
│ UserInfo.java
│
└─service
MyUserDetailsService.java
UserInfoService.java
平淡無奇的啟動類:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
UserInfo 對應資料庫表中的基本用戶信息,如下
public class UserInfo {
/**
* 用戶名
*/
private String username;
/**
* 密碼
*/
private String password;
/**
* 角色列表
*/
private List<String> roles;
//Getter & Settter
}
UserInfoService 模擬資料庫查詢,這裡做了簡化。
@Service
public class UserInfoService {
/**
* 查詢用戶信息
* 1. 移除資料庫交互,簡單實現。
* @param username 用戶名稱
* @return 結果
*/
public UserInfo queryUserInfo(final String username) {
UserInfo userInfo = new UserInfo();
if("user".equals(username) || "admin".equals(username)) {
userInfo.setUsername(username);
// 密碼可以在入庫的時候就進行加密
userInfo.setPassword("123456");
// 角色需要以 ROLE_ 開頭
userInfo.setRoles(Arrays.asList("ROLE_" + username));
return userInfo;
}
throw new UsernameNotFoundException(username+"對應信息不存在");
}
}
ps: ROLE_ 這個前綴主要是為了後面使用角色註解授權的時候需要,默認的前綴就是這個。
WebSecurityConfig.java 核心配置類這個類就是最核心的配置類了。
啪的一下,很快啊。
我們上來就是用了兩個註解,@EnableWebSecurity 啟用 web 安全,@EnableGlobalMethodSecurity(prePostEnabled = true) 啟用方法級別的安全校驗。
import com.github.houbb.spring.security.learn.springboot.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author 老馬嘯西風
* @since 1.0.0
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟方法級安全驗證
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private MyPasswordEncoder myPasswordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService)
.passwordEncoder(myPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() // 所有請求都需要驗證
.and()
.formLogin().permitAll() // 使用默認的登錄頁面,登錄頁面允許所有用戶訪問
.and()
.csrf().disable();// post請求要關閉csrf驗證,不然訪問報錯;實際開發中開啟,需要前端配合傳遞其他參數
}
}
一般情況下,MyUserDetailsService 和 MyPasswordEncoder 這兩個類都是需要我們自定義的。
MyUserDetailsService 用戶信息查詢我們只需要實現 UserDetailsService 接口,就可以實現對應的查詢實現。
這裡的授權信息,直接使用 SimpleGrantedAuthority 類。
import com.github.houbb.spring.security.learn.springboot.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 自定義根據名稱獲取用戶信息的實現
*
* @author binbin.hou
* @since 1.0.0
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = userInfoService.queryUserInfo(username);
// 授權信息構建
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : userInfo.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
return new User(
userInfo.getUsername(),
userInfo.getPassword(),
authorities
);
}
}
spring security 有很多內置的加密策略,這裡為了演示,我自定義了最簡單的 plainText 的策略,就是不做任何加密。
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* 密碼加密策略
* @author binbin.hou
* @since 1.0.0
*/
@Service
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return (String) rawPassword;
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.equals(encodedPassword);
}
}
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author binbin.hou
* @since 1.0.0
*/
@RestController
public class AuthController {
/**
* 查看登錄用戶信息
*/
@GetMapping("/auth")
public Authentication auth(){
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 只能 user 角色才能訪問該方法
* @return 結果
*/
@PreAuthorize("hasAnyRole('user')")
@GetMapping("/user")
public String user(){
return "user角色訪問";
}
/**
* 只能 admin 角色才能訪問該方法
* @return 結果
*/
@PreAuthorize("hasAnyRole('admin')")
@GetMapping("/admin")
public String admin(){
return "admin角色訪問";
}
}
這裡我們定義了 3 個方法,第一個方法是獲取當前用戶的登錄信息。
後面兩個方法都是通過 @PreAuthorize 指定訪問需要的角色信息。
測試驗證登錄看到這裡的小夥伴也許會問,你怎麼不寫 login 對應實現呢?
實際上 springboot 把默認的 login/logout 都做了封裝,我們平時學習可以直接使用。
如果是真實生產,一般需要自己寫。
我們啟動應用,瀏覽器訪問 http://localhost:8080/auth 想查看登錄信息,因為所有請求都需要登錄驗證,所以會被重定向到登錄頁面
http://localhost:8080/login
輸入信息我們輸入 admin/123456 以 admin 的角色登錄。
則可以獲取到授權信息如下:
{"authorities":[{"authority":"ROLE_admin"}],"details":{"remoteAddress":"127.0.0.1","sessionId":"8871ED88F86B4CD67EAA2FBAC40C68C2"},"authenticated":true,"principal":{"password":null,"username":"admin","authorities":[{"authority":"ROLE_admin"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"credentials":null,"name":"admin"}
我們訪問 http://localhost:8080/admin,頁面返回
admin角色訪問
我們訪問 http://localhost:8080/user,頁面返回
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Jan 12 23:26:31 CST 2021
There was an unexpected error (type=Forbidden, status=403).
Forbidden
也就是 403 權限不足,訪問被拒絕。
小結一個最簡單的 spring security 與 springboot 整合就這樣搞定了,是不是特別簡單呢?
類比 shiro,spring security 肯定也是在登錄的時候為我們做了相關的密碼驗證+授權信息保存,通過攔截器對請求進行攔截校驗。
不得不說,spring 的封裝確實優秀,後面我們進一步深入的學習,做到熟練地使用 spring security。
希望本文對你有所幫助,如果喜歡,歡迎點讚收藏轉發一波。
我是老馬,期待與你的下次相遇。