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


目錄

SpringSecurity權限管理系統實戰—一、項目簡介和開發環境准備
SpringSecurity權限管理系統實戰—二、日志、接口文檔等實現
SpringSecurity權限管理系統實戰—三、主要頁面及接口實現
SpringSecurity權限管理系統實戰—四、整合SpringSecurity(上)
SpringSecurity權限管理系統實戰—五、整合SpringSecurity(下)
SpringSecurity權限管理系統實戰—六、SpringSecurity整合jwt
SpringSecurity權限管理系統實戰—七、處理一些問題
SpringSecurity權限管理系統實戰—八、AOP 記錄用戶日志、異常日志
SpringSecurity權限管理系統實戰—九、數據權限的配置

前言

這幾天的時間去弄博客了,這個項目就被擱在一邊了。
在之前我是用wordpress來搭的博客,用的阿里雲的學生機,就卡的不行,體驗極差,也沒有發布過多少內容。后來又想着自己寫一個博客系統,后台部分已經開發了大半,懶癌犯了,就一直擱置了(圖片上的所有能點擊的接口都實現了)。現在回過去一看,接口十分混亂,冗余。可能不會再用來作為自己的博客了(隨便再寫寫,做個畢設項目吧)

在這里插入圖片描述

然后又想着用靜態博客,繞來繞去后,最終選用了vuepress來搭建靜態博客,部署的時候又順帶着復習了下git的知識(平時idea插件用的搞得我git命令都忘得差不多了)。現在的博客是根據vuepress-theme-roco主題魔改的,給張照片感受下

在這里插入圖片描述

已經部署到github pages。可以訪問www.codermy.cn查看。 目前還沒有備案成功,尚未配置cdn,所以可能會加載有點慢。國內也可以訪問 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的依賴即可

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

這個依賴在最初給的pom中已經有了,不過給注釋了,取消掉就可以,其余什么都不用做,啟動項目。

啟動完成后,我們訪問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>();
        // 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<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);

重啟項目,這時需要我們輸入正確的驗證碼后才能進行登錄
剩下的一些我們下一節再來完成
在這里插入圖片描述
本系列giteegithub中同步更新


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM