SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)
阿新 • • 發佈:2020-08-18
## 目錄
[SpringSecurity許可權管理系統實戰—一、專案簡介和開發環境準備](https://www.cnblogs.com/codermy/p/13516372.html)
[SpringSecurity許可權管理系統實戰—二、日誌、介面文件等實現](https://www.cnblogs.com/codermy/p/13516369.html)
[SpringSecurity許可權管理系統實戰—三、主要頁面及介面實現](https://www.cnblogs.com/codermy/p/13516379.html)
[SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)](https://blog.csdn.net/HYDCS/article/details/107367064)
[SpringSecurity許可權管理系統實戰—五、整合SpringSecurity(下)](https://blog.csdn.net/HYDCS/article/details/107510905)
[SpringSecurity許可權管理系統實戰—六、SpringSecurity整合jwt](https://blog.csdn.net/HYDCS/article/details/107732916)
[SpringSecurity許可權管理系統實戰—七、處理一些問題](https://blog.csdn.net/HYDCS/article/details/107765898)
[SpringSecurity許可權管理系統實戰—八、AOP 記錄使用者日誌、異常日誌](https://blog.csdn.net/HYDCS/article/details/107965522)
## 前言
這幾天的時間去弄部落格了,這個專案就被擱在一邊了。
在之前我是用wordpress來搭的部落格,用的阿里雲的學生機,就卡的不行,體驗極差,也沒有釋出過多少內容。後來又想著自己寫一個部落格系統,後臺部分已經開發了大半,懶癌犯了,就一直擱置了(圖片上的所有能點選的介面都實現了)。現在回過去一看,介面十分混亂,冗餘。可能不會再用來作為自己的部落格了(隨便再寫寫,做個畢設專案吧)
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720120637442.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center)
然後又想著用靜態部落格,繞來繞去後,最終選用了[vuepress](https://www.vuepress.cn/)來搭建靜態部落格,部署的時候又順帶著複習了下git的知識(平時idea外掛用的搞得我git命令都忘得差不多了)。現在的部落格是根據vuepress-theme-roco主題魔改的,給張照片感受下
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720120702214.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center)
已經部署到github pages。可以訪問[www.codermy.cn](www.codermy.cn)檢視。 目前還沒有備案成功,尚未配置cdn,所以可能會載入有點慢。國內也可以訪問 [witmy.gitee.io](witmy.gitee.io) 檢視。
## **一、Spring Security 介紹**
Spring Security 是Spring專案之中的一個安全模組,可以非常方便與spring專案整合。自從有了 Spring Boot 之後,Spring Boot 對於 Spring Security 提供了 自動化配置方案,可以零配置使用 Spring Security。
其實Spring Security 最早不叫 Spring Security ,叫 Acegi Security,後來才發展成為Spring的子專案。由於SpringBoot的大火,讓Spring系列的技術都得到了非常多的關注度,SpringSecurity同樣也沾了一把光。
一般來說,Web 應用的安全性包括兩部分:
1. 使用者**認證**(Authentication)
2. 使用者**授權**(Authorization)
簡單來說,認證就是登入,授權其實就是許可權的鑑別,看使用者是否具備相應請求的許可權。
## 二、整合SpringSecurity
在SpringBoot中想要使用SpringSecurity,只要新增SpringSecurity的依賴即可
```pom
org.springframework.boot
spring-boot-starter-security
```
這個依賴在最初給的pom中已經有了,不過給註釋了,取消掉就可以,其餘什麼都不用做,啟動專案。
啟動完成後,我們訪問或者其中的任何介面,都會重定向到登入頁面。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200715175246293.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70)
SpringSecurity預設的使用者名稱是user,密碼則在啟動專案時會列印在控制檯上。
```bash
Using generated security password: 21d26148-7f1e-403a-9041-1bc62a034871
```
`21d26148-7f1e-403a-9041-1bc62a034871`就是密碼,每次啟動都會分配不一樣的密碼。SpringSecurity同樣支援自定義密碼,只要在application.yml中簡單配置一下即可
```yml
spring:
security:
user:
name: admin
password: 123456
```
輸入使用者名稱密碼,登入後就能訪問index頁面了
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020071517532698.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70)
## 三、自定義登入頁
SpringSecurity預設的登入頁在SpringBoot2.0之後已經做過升級了,以前的更醜,就是一個沒有樣式的form表單。現在這個雖然好看了不少,但是感覺還是單調了些。
那麼我們需要新建一個SpringSecurityConfig類繼承WebSecurityConfigurerAdapter
```java
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/PearAdmin/**");//放行靜態資源
}
/**
* anyRequest | 匹配所有請求路徑
* access | SpringEl表示式結果為true時可以訪問
* anonymous | 匿名可以訪問
* denyAll | 使用者不能訪問
* fullyAuthenticated | 使用者完全認證可以訪問(非remember-me下自動登入)
* hasAnyAuthority | 如果有引數,引數表示許可權,則其中任何一個許可權可以訪問
* hasAnyRole | 如果有引數,引數表示角色,則其中任何一個角色可以訪問
* hasAuthority | 如果有引數,引數表示許可權,則其許可權可以訪問
* hasIpAddress | 如果有引數,引數表示IP地址,如果使用者IP和引數匹配,則可以訪問
* hasRole | 如果有引數,引數表示角色,則其角色可以訪問
* permitAll | 使用者可以任意訪問
* rememberMe | 允許通過remember-me登入的使用者訪問
* authenticated | 使用者登入後可訪問
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")//登入頁面
.loginProcessingUrl("/login")//登入介面
.permitAll()
.and()
.csrf().disable();//關閉csrf
}
}
```
把login.html移動到static目錄下,不要忘記把form表單的action替換成/login
```html
M-S-P Admin
Spring Security 權 限 管 理 系 統 實 戰
```
重啟專案檢視
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200715175350939.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center)
## 四、動態獲取選單
目前我們的專案還是根據PeaAdmin的menu.json來獲取的選單。這明顯不行,沒有許可權的使用者登入後點來點去,發現什麼都用不了,這對使用者體驗來說非常差。所有要根據使用者的id來動態的生成選單。
首先看一下menu.json的格式。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200715175409856.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70)
之後的返回的json格式也要像這樣才能被正確解析。
新建一個MenuIndexDto用於封裝資料
```java
@Data
public class MenuIndexDto implements Serializable {
private Integer id;
private Integer parentId;
private String title;
private String icon;
private Integer type;
private String href;
private List children;
}
```
MenuDao中新增通過使用者id查詢選單的方法
```java
@Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type " +
"FROM my_role_user sru " +
"INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " +
"LEFT JOIN my_menu sp ON srp.menu_id = sp.id " +
"WHERE " +
"sru.user_id = #{userId}")
@Result(property = "title",column = "name")
@Result(property = "href",column = "url")
List listByUserId(@Param("userId")Integer userId);
```
MenuService
```java
List getMenu(Integer userId);
```
MenuServiceImpl
```java
@Override
public List getMenu(Integer userId) {
List list = menuDao.listByUserId(userId);
List result = TreeUtil.parseMenuTree(list);
return result;
}
```
這裡我寫了一個工具方法,用於轉換返回格式。TreeUtil新增如下方法
```java
public static List parseMenuTree(List list){
List result = new ArrayList();
// 1、獲取第一級節點
for (MenuIndexDto menu : list) {
if(menu.getParentId() == 0) {
result.add(menu);
}
}
// 2、遞迴獲取子節點
for (MenuIndexDto parent : result) {
parent = recursiveTree(parent, list);
}
return result;
}
public static MenuIndexDto recursiveTree(MenuIndexDto parent, List list) {
Listchildren = new ArrayList<>();
for (MenuIndexDto menu : list) {
if (Objects.equals(parent.getId(), menu.getParentId())) {
children.add(menu);
}
parent.setChildren(children);
}
return parent;
}
```
MenuController新增如下方法
```java
@GetMapping(value = "/index")
@ResponseBody
@ApiOperation(value = "通過使用者id獲取選單")
public List getMenu(Integer userId) {
return menuService.getMenu(userId);
}
```
在index.html檔案中把選單資料載入地址 先換成`/api/menu/index/?userId=1`(這裡先寫死,之後自定義SpringSecurity的userdetail時再改)
啟動專案,檢視效果
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200715175439741.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70)
這裡顯示拒絕連結是因為SpringSecurity預設拒絕frame中訪問。這裡我們可以寫一個SuccessHandler設定Header,或者在SpringSecurityConfig重寫的configure方法中新增如下配置
```java
http.headers().frameOptions().sameOrigin();
```
再重啟專案,就可以正常訪問了。
## 五、改寫選單路由
之前選單的路由我們是寫再HelloController中的,現在我們規定下格式。新建AdminController
```java
@Controller
@RequestMapping("/api")
@Api(tags = "系統:選單路由")
public class AdminController {
@Autowired
private MenuService menuService;
@GetMapping(value = "/index")
@ResponseBody
@ApiOperation(value = "通過使用者id獲取選單")
public List getMenu(Integer userId) {
return menuService.getMenu(userId);
}
@GetMapping("/console")
public String console(){
return "console/console1";
}
@GetMapping("/403")
public String error403(){
return "error/403";
}
@GetMapping("/404")
public String error404(){
return "error/404";
}
@GetMapping("/500")
public String error500(){
return "error/500";
}
@GetMapping("/admin")
public String admin(){
return "index";
}
}
```
再去相應頁面改寫下路由就可以
## 六、圖形驗證碼
驗證碼主要是防止機器大規模註冊,機器暴力破解資料密碼等危害。
[EasyCaptcha](https://gitee.com/whvse/EasyCaptcha)是一個Java圖形驗證碼生成工具,可生成的型別有如下幾種
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720120740799.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center)
首先引入maven
```xml
com.github.whvcse
easy-captcha
1.6.2
```
新建一個CaptchaController
```java
@Controller
public class CaptchaController {
@RequestMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
CaptchaUtil.out(request, response);
}
}
```
再login.html 密碼所在的div後面新增如下程式碼(這裡我添加了一下css格式,具體不貼了,自己操作吧)
```xml
```
重啟專案來看一下
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720120752866.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center)
目前只是讓驗證碼在前端繪製了出來,我們如果想要使用,還需要自定義一個過濾器
新建VerifyCodeFilter繼承OncePerRequestFilter
```java
@Component
public class VerifyCodeFilter extends OncePerRequestFilter {
private String defaultFilterProcessUrl = "/login";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if ("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
// 登入請求校驗驗證碼,非登入請求不用校驗
HttpSession session = request.getSession();
String requestCaptcha = request.getParameter("captcha");
String genCaptcha = (String) request.getSession().getAttribute("captcha");//驗證碼的資訊存放在seesion種,具體看EasyCaptcha官方解釋
if (StringUtils.isEmpty(requestCaptcha)){
session.removeAttribute("captcha");//刪除快取裡的驗證碼資訊
throw new AuthenticationServiceException("驗證碼不能為空!");
}
if (!genCaptcha.toLowerCase().equals(requestCaptcha.toLowerCase())) {
session.removeAttribute("captcha");
throw new AuthenticationServiceException("驗證碼錯誤!");
}
}
chain.doFilter(request, response);
}
}
```
最後在SpringSecurity種配置該過濾器
```java
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private VerifyCodeFilter verifyCodeFilter;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/PearAdmin/**");//放行靜態資源
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin();
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/captcha").permitAll()//任何人都能訪問這個請求
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")//登入頁面 不設限訪問
.loginProcessingUrl("/login")//攔截的請求
.successForwardUrl("/api/admin")
.permitAll()
.and()
.csrf().disable();//關閉csrf
}
}
```
即
```java
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
```
重啟專案,這時需要我們輸入正確的驗證碼後才能進行登入
剩下的一些我們下一節再來完成
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720120939562.png)
本系列[gitee](https://gitee.com/witmy/my-springsecurity-plus)和[github](https://github.com/witmy/my-springsecurity-plus)中同