前面我們學習了 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
Application.java
平淡無奇的啟動類:
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
UserInfo & UserInfoServiceUserInfo 對應資料庫表中的基本使用者資訊,如下
public class UserInfo { /** * 使用者名稱 */ private String username; /** * 密碼 */ private String password; /** * 角色列表 */ private List<String> roles; //Getter & Settter}
UserInfoService 模擬資料庫查詢,這裡做了簡化。
@Servicepublic 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) 啟用方法級別的安全校驗。
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 */@Servicepublic 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 ); }}
MyPasswordEncoder 密碼加密策略spring security 有很多內建的加密策略,這裡為了演示,我自定義了最簡單的 plainText 的策略,就是不做任何加密。
import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;/** * 密碼加密策略 * @author binbin.hou * @since 1.0.0 */@Servicepublic 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); }}
AuthController 控制器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 */@RestControllerpublic 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 PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.Tue Jan 12 23:26:31 CST 2021There was an unexpected error (type=Forbidden, status=403).Forbidden
也就是 403 許可權不足,訪問被拒絕。
小結一個最簡單的 spring security 與 springboot 整合就這樣搞定了,是不是特別簡單呢?
類比 shiro,spring security 肯定也是在登入的時候為我們做了相關的密碼驗證+授權資訊儲存,透過攔截器對請求進行攔截校驗。
不得不說,spring 的封裝確實優秀,後面我們進一步深入地學習,做到熟練地使用 spring security。
我是老馬,期待與你的下次相遇。