SpringSecurity權限管理系統實戰—1、項目簡介和開發環境準備
SpringSecurity權限管理系統實戰—2、日誌、接口文檔等實現
SpringSecurity權限管理系統實戰—3、主要頁面及接口實現
SpringSecurity權限管理系統實戰—4、整合SpringSecurity(上)
SpringSecurity權限管理系統實戰—5、整合SpringSecurity(下)
SpringSecurity權限管理系統實戰—6、SpringSecurity整合jwt
SpringSecurity權限管理系統實戰—7、處理一些問題
SpringSecurity權限管理系統實戰—8、AOP 記錄用戶日誌、異常日誌css
這幾天的時間去弄博客了,這個項目就被擱在一邊了。
在以前我是用wordpress來搭的博客,用的阿里雲的學生機,就卡的不行,體驗極差,也沒有發佈過多少內容。後來又想着本身寫一個博客系統,後臺部分已經開發了大半,懶癌犯了,就一直擱置了(圖片上的全部能點擊的接口都實現了)。如今回過去一看,接口十分混亂,冗餘。可能不會再用來做爲本身的博客了(隨便再寫寫,作個畢設項目吧)html
而後又想着用靜態博客,繞來繞去後,最終選用了vuepress來搭建靜態博客,部署的時候又順帶着複習了下git的知識(平時idea插件用的搞得我git命令都忘得差很少了)。如今的博客是根據vuepress-theme-roco主題魔改的,給張照片感覺下前端
已經部署到github pages。能夠訪問www.codermy.cn查看。 目前尚未備案成功,還沒有配置cdn,因此可能會加載有點慢。國內也能夠訪問 witmy.gitee.io 查看。vue
Spring Security 是Spring項目之中的一個安全模塊,能夠很是方便與spring項目集成。自從有了 Spring Boot 以後,Spring Boot 對於 Spring Security 提供了 自動化配置方案,能夠零配置使用 Spring Security。java
其實Spring Security 最先不叫 Spring Security ,叫 Acegi Security,後來才發展成爲Spring的子項目。因爲SpringBoot的大火,讓Spring系列的技術都獲得了很是多的關注度,SpringSecurity一樣也沾了一把光。jquery
通常來講,Web 應用的安全性包括兩部分:git
簡單來講,認證就是登陸,受權其實就是權限的鑑別,看用戶是否具有相應請求的權限。github
在SpringBoot中想要使用SpringSecurity,只要添加SpringSecurity的依賴便可web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
這個依賴在最初給的pom中已經有了,不過給註釋了,取消掉就能夠,其他什麼都不用作,啓動項目。spring
啓動完成後,咱們訪問http://localhost:8080或者其中的任何接口,都會重定向到登陸頁面。
SpringSecurity默認的用戶名是user,密碼則在啓動項目時會打印在控制檯上。
Using generated security password: 21d26148-7f1e-403a-9041-1bc62a034871
21d26148-7f1e-403a-9041-1bc62a034871
就是密碼,每次啓動都會分配不同的密碼。SpringSecurity一樣支持自定義密碼,只要在application.yml中簡單配置一下便可
spring: security: user: name: admin password: 123456
輸入用戶名密碼,登陸後就能訪問index頁面了
SpringSecurity默認的登陸頁在SpringBoot2.0以後已經作過升級了,之前的更醜,就是一個沒有樣式的form表單。如今這個雖然好看了很多,可是感受仍是單調了些。
那麼咱們須要新建一個SpringSecurityConfig類繼承WebSecurityConfigurerAdapter
@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
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title></title> <link rel="stylesheet" href="/PearAdmin/admin/css/pearForm.css" /> <link rel="stylesheet" href="/PearAdmin/component/layui/css/layui.css" /> <link rel="stylesheet" href="/PearAdmin/admin/css/pearButton.css" /> <link rel="stylesheet" href="/PearAdmin/assets/login.css" /> </head> <body background="PearAdmin/admin/images/background.svg" > <form class="layui-form" action="/login" method="post"> <div class="layui-form-item"> <img class="logo" src="PearAdmin/admin/images/logo.png" /> <div class="title">M-S-P Admin</div> <div class="desc"> Spring Security 權 限 管 理 系 統 實 戰 </div> </div> <div class="layui-form-item"> <input id="username" name="username" placeholder="用戶名 : " type="text" hover class="layui-input" /> </div> <div class="layui-form-item"> <input d="password" name="password" placeholder="密 碼 : " type="password" hover class="layui-input" /> </div> <div class="layui-form-item"> <input type="checkbox" name="" title="記住密碼" lay-skin="primary" checked> </div> <div class="layui-form-item"> <button style="background-color: #5FB878!important;" class="pear-btn pear-btn-primary login"> 登 入 </button> </div> </form> <script src="/PearAdmin/component/layui/layui.js" charset="utf-8"></script> <script> layui.use(['form', 'element','jquery'], function() { var form = layui.form; var element = layui.element; var $ = layui.jquery; $("body").on("click",".login",function(){ location.href="index" }) }) </script> </body> </html>
重啓項目查看
目前咱們的項目仍是根據PeaAdmin的menu.json來獲取的菜單。這明顯不行,沒有權限的用戶登陸後點來點去,發現什麼都用不了,這對用戶體驗來講很是差。全部要根據用戶的id來動態的生成菜單。
首先看一下menu.json的格式。
以後的返回的json格式也要像這樣才能被正確解析。
新建一個MenuIndexDto用於封裝數據
@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<MenuIndexDto> children; }
MenuDao中新增經過用戶id查詢菜單的方法
@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<MenuIndexDto> listByUserId(@Param("userId")Integer userId);
MenuService
List<MenuIndexDto> getMenu(Integer userId);
MenuServiceImpl
@Override public List<MenuIndexDto> getMenu(Integer userId) { List<MenuIndexDto> list = menuDao.listByUserId(userId); List<MenuIndexDto> result = TreeUtil.parseMenuTree(list); return result; }
這裏我寫了一個工具方法,用於轉換返回格式。TreeUtil添加以下方法
public static List<MenuIndexDto> parseMenuTree(List<MenuIndexDto> list){ List<MenuIndexDto> result = new ArrayList<MenuIndexDto>(); // 一、獲取第一級節點 for (MenuIndexDto menu : list) { if(menu.getParentId() == 0) { result.add(menu); } } // 二、遞歸獲取子節點 for (MenuIndexDto parent : result) { parent = recursiveTree(parent, list); } return result; } public static MenuIndexDto recursiveTree(MenuIndexDto parent, List<MenuIndexDto> list) { List<MenuIndexDto>children = new ArrayList<>(); for (MenuIndexDto menu : list) { if (Objects.equals(parent.getId(), menu.getParentId())) { children.add(menu); } parent.setChildren(children); } return parent; }
MenuController添加以下方法
@GetMapping(value = "/index") @ResponseBody @ApiOperation(value = "經過用戶id獲取菜單") public List<MenuIndexDto> getMenu(Integer userId) { return menuService.getMenu(userId); }
在index.html文件中把菜單數據加載地址 先換成/api/menu/index/?userId=1
(這裏先寫死,以後自定義SpringSecurity的userdetail時再改)
啓動項目,查看效果
這裏顯示拒絕連接是由於SpringSecurity默認拒絕frame中訪問。這裏咱們能夠寫一個SuccessHandler設置Header,或者在SpringSecurityConfig重寫的configure方法中添加以下配置
http.headers().frameOptions().sameOrigin();
再重啓項目,就能夠正常訪問了。
以前菜單的路由咱們是寫再HelloController中的,如今咱們規定下格式。新建AdminController
@Controller @RequestMapping("/api") @Api(tags = "系統:菜單路由") public class AdminController { @Autowired private MenuService menuService; @GetMapping(value = "/index") @ResponseBody @ApiOperation(value = "經過用戶id獲取菜單") public List<MenuIndexDto> 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是一個Java圖形驗證碼生成工具,可生成的類型有以下幾種
首先引入maven
<dependencies> <dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency> </dependencies>
新建一個CaptchaController
@Controller public class CaptchaController { @RequestMapping("/captcha") public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception { CaptchaUtil.out(request, response); } }
再login.html 密碼所在的div後面添加以下代碼(這裏我添加了一下css格式,具體不貼了,本身操做吧)
<div class="layui-form-item"> <input id="captcha" name="captcha" placeholder="驗 證 碼:" type="text" hover class="layui-verify" style="border: 1px solid #dcdfe6;"> <img src="/captcha" width="130px" height="44px" onclick="this.src=this.src+'?'+Math.random()" title="點擊刷新"/> </div>
重啓項目來看一下
目前只是讓驗證碼在前端繪製了出來,咱們若是想要使用,還須要自定義一個過濾器
新建VerifyCodeFilter繼承OncePerRequestFilter
@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種配置該過濾器
@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 } }
即
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
重啓項目,這時須要咱們輸入正確的驗證碼後才能進行登陸
剩下的一些咱們下一節再來完成
本系列gitee和github中同步更新