1. 程式人生 > >SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)

SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)

## 目錄 [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)中同