SpringSecurity權限管理系統實戰—4、整合SpringSecurity(上)

目錄

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

1、Spring Security 介紹

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

  1. 用戶認證(Authentication)
  2. 用戶受權(Authorization)

簡單來講,認證就是登陸,受權其實就是權限的鑑別,看用戶是否具有相應請求的權限。github

2、整合SpringSecurity

在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頁面了

在這裏插入圖片描述

3、自定義登陸頁

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>

重啓項目查看

在這裏插入圖片描述

4、動態獲取菜單

目前咱們的項目仍是根據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();

再重啓項目,就能夠正常訪問了。

5、改寫菜單路由

以前菜單的路由咱們是寫再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";
    }
}

再去相應頁面改寫下路由就能夠

6、圖形驗證碼

驗證碼主要是防止機器大規模註冊,機器暴力破解數據密碼等危害。

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);

重啓項目,這時須要咱們輸入正確的驗證碼後才能進行登陸
剩下的一些咱們下一節再來完成
在這裏插入圖片描述
本系列giteegithub中同步更新

相關文章
相關標籤/搜索