SpringBoot工程中Spring Security應(yīng)用實(shí)踐記錄流程分析
SpringSecurity 應(yīng)用
簡(jiǎn)介
Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應(yīng)用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉(zhuǎn)Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應(yīng)用系統(tǒng)提供聲明式的安全訪問控制功能,減少了為企業(yè)系統(tǒng)安全控制編寫大量重復(fù)代碼的工作。
認(rèn)證授權(quán)分析
用戶在進(jìn)行資源訪問時(shí),要求系統(tǒng)要對(duì)用戶進(jìn)行權(quán)限控制,其具體流程如圖-1所示:

SpringSecurity 架構(gòu)設(shè)計(jì)
鳥瞰SpringSecurity 基本技術(shù)架構(gòu),例如:

綠色部分是認(rèn)證過濾器,需要我們自己配置,可以配置多個(gè)認(rèn)證過濾器。認(rèn)證過濾器可以使用 Spring Security 提供的認(rèn)證過濾器,也可以自定義過濾器(例如:短信驗(yàn)證)。認(rèn)證過濾器要在 configure(HttpSecurity http)方法中配置,沒有配置不生效。下面會(huì)重點(diǎn)介紹以下三個(gè)過濾器:
UsernamePasswordAuthenticationFilter 過濾器:該過濾器會(huì)攔截前端提交的 POST 方式的登錄表單請(qǐng)求,并進(jìn)行身份認(rèn)證。
BasicAuthenticationFilter:檢測(cè)和處理 http basic 認(rèn)證。
ExceptionTranslationFilter 過濾器:該過濾器不需要我們配置,對(duì)于前端提交的請(qǐng)求會(huì)直接放行,捕獲后續(xù)拋出的異常并進(jìn)行處理(例如:權(quán)限訪問限制)。
FilterSecurityInterceptor 過濾器:該過濾器是過濾器鏈的最后一個(gè)過濾器,根據(jù)資源權(quán)限配置來判斷當(dāng)前請(qǐng)求是否有權(quán)限訪問對(duì)應(yīng)的資源。如果訪問受限會(huì)拋出相關(guān)異常,并由 ExceptionTranslationFilter 過濾器進(jìn)行捕獲和處理。
快速入門實(shí)踐
創(chuàng)建項(xiàng)目
創(chuàng)建security項(xiàng)目,其pom.xml文件內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.2.RELEASE</version>
</parent>
<groupId>com.cy</groupId>
<artifactId>05-security</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
添加項(xiàng)目依賴
第一步:創(chuàng)建項(xiàng)目,其pom.xml文件核心依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
第二步:?jiǎn)?dòng)服務(wù)(依賴添加以后會(huì)默認(rèn)添加一個(gè)tomcat,端口8080)
服務(wù)啟動(dòng)之后,你會(huì)發(fā)現(xiàn),控制臺(tái)會(huì)出現(xiàn)一個(gè)隨機(jī)的密碼,用于訪問當(dāng)前系統(tǒng),默認(rèn)用戶名是user,密碼就是控制臺(tái)上的密碼,如圖所示:

啟動(dòng)服務(wù)訪問測(cè)試
服務(wù)啟動(dòng)后,打開瀏覽器進(jìn)行訪問,如圖所示:

輸入賬號(hào)(默認(rèn)用戶名為user)和密碼登陸成功默認(rèn)為如下頁(yè)面.

其中,出現(xiàn)這個(gè)頁(yè)面表示還沒有配置登陸成功頁(yè)面,這個(gè)資源頁(yè)面現(xiàn)在還不存在,可以在項(xiàng)目的resources目錄下創(chuàng)建static目錄(假如沒有此目錄),然后在此目錄下創(chuàng)建index.html頁(yè)面,內(nèi)容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h1>The Index Page</h1>
</div>
</body>
</html>
此時(shí),再次啟動(dòng)服務(wù)進(jìn)行登陸,呈現(xiàn)登陸成功的效果,如圖所示:

自定義認(rèn)證邏輯
認(rèn)證流程分析

定義security配置類
定義配置類,基于此類配置認(rèn)證和授權(quán)邏輯,例如:
package com.cy.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 認(rèn)證授權(quán)管理器對(duì)用戶輸入的密碼與數(shù)據(jù)庫(kù)中存儲(chǔ)的密碼進(jìn)行比對(duì)時(shí),
* 需要對(duì)這個(gè)密碼加密,加密算法需要我們自己指定
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
//關(guān)閉跨域攻擊
http.csrf().disable();
//自定義登陸表單
http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
//請(qǐng)求資源的認(rèn)證配置
http.authorizeRequests()
.antMatchers("/login","/login.html")
.permitAll()
.anyRequest().authenticated();
}
}
定義數(shù)據(jù)訪問層對(duì)象
定義數(shù)據(jù)訪問層對(duì)象,基于此對(duì)象實(shí)現(xiàn)用戶及用戶權(quán)限信息的獲取,例如:
package com.cy.security.dao;
import com.cy.security.domain.SysUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 基于用戶名獲取用戶信息
* @param username
* @return
*/
@Select("select id,username,password,status " +
"from tb_users " +
"where username=#{username}")
SysUser selectUserByUsername(String username);
/**
* 基于用戶id查詢用戶權(quán)限
* @param userId 用戶id
* @return 用戶的權(quán)限
* 涉及到的表:tb_user_roles,tb_role_menus,tb_menus
*/
@Select("select distinct m.permission " +
"from tb_user_roles ur join tb_role_menus rm on ur.role_id=rm.role_id" +
" join tb_menus m on rm.menu_id=m.id " +
"where ur.user_id=#{userId} and m.permission is not null")
List<String> selectUserPermissions(Long userId);
}
定義UserDetailService接口實(shí)現(xiàn)類
Spring Security 提供了一個(gè)UserDetailService接口,我們可以基于此接口實(shí)現(xiàn)類,實(shí)現(xiàn)用戶信息的獲取和封裝,例如:
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* 客戶端點(diǎn)擊登陸時(shí),添加些用戶信息會(huì)傳給此方法
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.基于用戶名查詢用戶信息
SysUser user=userMapper.selectUserByUsername(username);
//...判斷自己寫...
System.out.println(user);
//2.查詢用戶登陸用戶權(quán)限
List<String> permissions=userMapper.selectUserPermissions(user.getId());
System.out.println(permissions);
//3.封裝用戶信息并返回,將用戶信息交給認(rèn)證管理器,認(rèn)證授權(quán)管理器負(fù)責(zé)對(duì)用戶輸入的信息進(jìn)行認(rèn)證和授權(quán)
List<GrantedAuthority> authorityList =
AuthorityUtils.createAuthorityList(permissions.toArray(new String[]{}));
return new User(username, user.getPassword(),authorityList);
}
}
自定義登陸頁(yè)面
在resources的static目錄下創(chuàng)建login.html頁(yè)面,例如:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link rel="external nofollow" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>login</title>
</head>
<body>
<div class="container"id="app">
<h3>Please Login</h3>
<form>
<div class="mb-3">
<label for="usernameId" class="form-label">Username</label>
<input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="passwordId" class="form-label">Password</label>
<input type="password" v-model="password" class="form-control" id="passwordId">
</div>
<button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm=new Vue({
el:"#app",//定義監(jiān)控點(diǎn),vue底層會(huì)基于此監(jiān)控點(diǎn)在內(nèi)存中構(gòu)建dom樹
data:{ //此對(duì)象中定義頁(yè)面上要操作的數(shù)據(jù)
username:"",
password:""
},
methods: {//此位置定義所有業(yè)務(wù)事件處理函數(shù)
doLogin() {
//1.定義url
let url = "http://localhost:8080/login"
//2.定義參數(shù)
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
debugger
//3.發(fā)送異步請(qǐng)求
axios.post(url, params).then((response) => {
alert("login ok");
location.href="/index.html" rel="external nofollow"
})
}
}
});
</script>
</body>
</html>
啟動(dòng)服務(wù)進(jìn)行訪問測(cè)試

授權(quán)邏輯設(shè)計(jì)及實(shí)現(xiàn)
修改授權(quán)配置類
在權(quán)限配置類上添加啟用全局方法訪問控制注解,例如
package com.cy.auth.config;
//這個(gè)配置類是配置Spring-Security的,
//prePostEnabled= true表示啟動(dòng)權(quán)限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {
……
}
定義資源訪問對(duì)象
package com.cy.res.controller;
@RequestMapping("/res")
@RestController
public class ResourceController {
@PreAuthorize("hasAuthority('sys:res:view')")
@RequestMapping("/retrieve")
public String doRetrieve(){
return "select resource ok";
}
@PreAuthorize("hasAuthority('sys:res:create')")
@RequestMapping("/create")
public String doCreate(){
return "create resource";
}
}
其中,@PreAuthorize注解描述方法時(shí),用于告訴系統(tǒng)訪問此方法時(shí)需要進(jìn)行權(quán)限檢測(cè)。需要具備指定權(quán)限才可以訪問。例如:
啟動(dòng)服務(wù)實(shí)現(xiàn)訪問測(cè)試
打開瀏覽器分別輸入http://localhost:8080/res/create和http://localhost:8080/res/select進(jìn)行測(cè)試分析。
總結(jié)(Summary)
本章節(jié)主要是對(duì)spring security 在springboot平臺(tái)下是如何使用的。
到此這篇關(guān)于SpringBoot工程中Spring Security應(yīng)用實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Security應(yīng)用實(shí)踐內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot整合Security實(shí)現(xiàn)權(quán)限控制框架(案例詳解)
- SpringBoot項(xiàng)目實(shí)現(xiàn)關(guān)閉數(shù)據(jù)庫(kù)配置和springSecurity
- SpringBoot+Spring Security無法實(shí)現(xiàn)跨域的解決方案
- springboot+springsecurity如何實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證
- SpringSecurity整合springBoot、redis實(shí)現(xiàn)登錄互踢功能
- SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證
- Springboot WebFlux集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的示例
- Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- SpringBoot使用Spring Security實(shí)現(xiàn)登錄注銷功能
- SpringBoot+SpringSecurity 不攔截靜態(tài)資源的實(shí)現(xiàn)
相關(guān)文章
SpringBoot通過請(qǐng)求對(duì)象獲取輸入流無數(shù)據(jù)
這篇文章主要介紹了使用SpringBoot通過請(qǐng)求對(duì)象獲取輸入流無數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
springboot使用redis對(duì)單個(gè)對(duì)象進(jìn)行自動(dòng)緩存更新刪除的實(shí)現(xiàn)
本文主要介紹了springboot使用redis對(duì)單個(gè)對(duì)象進(jìn)行自動(dòng)緩存更新刪除的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Activiti explorer.war示例工程使用過程圖解
這篇文章主要介紹了Activiti explorer.war示例工程使用過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Java中對(duì)List去重 Stream去重的解決方法
這篇文章主要介紹了Java中對(duì)List去重, Stream去重的問題解答,文中給大家介紹了Java中List集合去除重復(fù)數(shù)據(jù)的方法,需要的朋友可以參考下2018-04-04
詳解關(guān)于SpringBoot的外部化配置使用記錄
這篇文章主要介紹了詳解關(guān)于SpringBoot的外部化配置使用記錄,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
java時(shí)間段查詢將00:00:00更換成23:59:59
本文主要介紹了java時(shí)間段查詢將00:00:00更換成23:59:59,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
基于獲取JAVA路徑,包括CLASSPATH外的路徑的方法詳解
本篇文章是對(duì)獲取JAVA路徑,包括CLASSPATH外的路徑的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

