目錄
?一、簡介
1.1 什么是Spring Security
1.2?工作原理
1.3?為什么選擇Spring Security
1.4?HttpSecurity 介紹??
二、用戶認(rèn)證
2.1 導(dǎo)入依賴與配置
2.2?用戶對象UserDetails
2.3?業(yè)務(wù)對象UserDetailsService
2.4 SecurityConfig配置
2.4.1?BCryptPasswordEncoder密碼編碼器
2.4.2?RememberMe 記住登錄信息
2.4.3?CSRF防御(跨站請求偽造)
三、用戶授權(quán)
3.1 授權(quán)介紹
?3.2?構(gòu)建 UserDetails 對象
3.2.1 準(zhǔn)備數(shù)據(jù)表
3.2.2? 設(shè)置用戶權(quán)限
3.3?修改SpringSecurity配置類
3.4?控制Controller層接口權(quán)限
3.5 相關(guān)頁面模版與工具類
3.6 權(quán)限測試
?一、簡介
1.1 什么是Spring Security
? ?Spring Security
是一個(gè)基于Spring
框架的安全性框架,可用于對Java應(yīng)用程序進(jìn)行身份驗(yàn)證、授權(quán)和其他安全性功能的添加。它不僅可以對Web應(yīng)用程序進(jìn)行保護(hù),還可以保護(hù)非Web環(huán)境下的應(yīng)用程序,如遠(yuǎn)程服務(wù)和命令行應(yīng)用程序等。Spring Security
提供了一系列可插拔的安全性特性,如基于標(biāo)記的身份驗(yàn)證、權(quán)限控制、會(huì)話管理、密碼加密等。它還支持多種安全性協(xié)議和標(biāo)準(zhǔn),如OAuth
、SAML
、OpenID
等,可與各種身份提供商集成。
1.2?工作原理
權(quán)限框架一般包含兩大核心模塊:認(rèn)證(Authentication)和授權(quán)(Authorization)。
-
認(rèn)證:認(rèn)證模塊負(fù)責(zé)驗(yàn)證用戶身份的合法性,生成認(rèn)證令牌,并保存到服務(wù)端會(huì)話中(如TLS)。
-
授權(quán):鑒權(quán)模塊負(fù)責(zé)從服務(wù)端會(huì)話內(nèi)獲取用戶身份信息,與訪問的資源進(jìn)行權(quán)限比對。
核心組件介紹:
-
AuthenticationManager
:管理身份驗(yàn)證,可以從多種身份驗(yàn)證方案中選擇一種。 -
Authentication
:用于驗(yàn)證用戶的身份。 -
SecurityContextHolder
:用于管理SecurityContext
的ThreadLocal
,以便在整個(gè)請求上下文中進(jìn)行訪問,方便用戶訪問。 -
AccessDecisionManager
:負(fù)責(zé)對訪問受保護(hù)的資源的請求進(jìn)行決策(即決定是否允許用戶訪問資源) -
AccessDecisionVoter
:是AccessDecisionManager的實(shí)現(xiàn)組件之一,它用于對用戶請求的訪問受保護(hù)的資源所需要的角色或權(quán)限進(jìn)行投票。 -
ConfigAttribute
:用于表示受保護(hù)資源或URL需要的訪問權(quán)限,它可以理解為是訪問控制策略的一部分
1.3?為什么選擇Spring Security
????????SpringBoot 沒有發(fā)布之前,Shiro 應(yīng)用更加廣泛,因?yàn)?Shiro 是一個(gè)強(qiáng)大且易用的 Java 安全框架,能夠非常清晰的處理身份驗(yàn)證、授權(quán)、管理會(huì)話以及密碼加密。利用其易于理解的API,可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。但是 Shiro 只是一個(gè)框架而已,其中的內(nèi)容需要自己的去構(gòu)建,前后是自己的,中間是Shiro幫我們?nèi)ゴ罱ê团渲煤玫摹?/p>
SpringBoot
發(fā)布后,隨著其快速發(fā)展,Spring Security
(前身叫做Acegi Security
) 重新進(jìn)入人們的視野。SpringBoot
解決了 Spring Security
各種復(fù)雜的配置,Spring Security
在我們進(jìn)行用戶認(rèn)證以及授予權(quán)限的時(shí)候,通過各種各樣的攔截器來控制權(quán)限的訪問,從而實(shí)現(xiàn)安全,也就是說 Spring Security
除了不能脫離 Spring
,Shiro
的功能它都有。
-
在用戶認(rèn)證方面,
Spring Security
框架支持主流的認(rèn)證方式,包括 HTTP 基本認(rèn)證、HTTP 表單驗(yàn)證、HTTP 摘要認(rèn)證、OpenID
和LDAP
等。 -
在用戶授權(quán)方面,
Spring Security
提供了基于角色的訪問控制和訪問控制列表(Access Control List,ACL
),可以對應(yīng)用中的領(lǐng)域?qū)ο筮M(jìn)行細(xì)粒度的控制。
Shiro
在這個(gè)環(huán)境下實(shí)際已經(jīng)不具備優(yōu)勢了。因?yàn)镾pring這個(gè)生態(tài)鏈現(xiàn)在是太強(qiáng)大了。
1.4?HttpSecurity 介紹??
HttpSecurity
是 Spring Security
的一個(gè)核心類,用于配置應(yīng)用程序的安全策略。
HttpSecurity
類通常包含許多方法,可以用于配置以下內(nèi)容:
-
HTTP 請求的安全策略,例如訪問控制、跨站點(diǎn)請求偽造 (CSRF) 防護(hù)等。
-
HTTP 驗(yàn)證的安全策略,例如基于表單、HTTP 基本身份驗(yàn)證、OAuth 等。
-
訪問受保護(hù)資源時(shí)所需的身份驗(yàn)證和授權(quán)方式。
方法 | 說明 |
---|---|
authorizeRequests() |
用于配置如何處理請求的授權(quán),默認(rèn)情況下所有的請求都需要進(jìn)行認(rèn)證和授權(quán)才能訪問受保護(hù)的資源 |
formLogin() |
用于配置基于表單的身份驗(yàn)證,包括自定義登錄頁面、登錄請求路徑、用戶名和密碼的參數(shù)名稱、登錄成功和失敗的跳轉(zhuǎn)等。 |
httpBasic() |
用于配置基于HTTP Basic 身份驗(yàn)證,包括定義使用的用戶名和密碼、realm 名稱等。 |
logout() |
用于配置退出登錄功能,包括定義退出登錄請求的URL、注銷成功后的跳轉(zhuǎn)URL、清除會(huì)話、刪除Remember-Me 令牌等。 |
csrf() |
用于配置跨站請求偽造保護(hù),包括定義CSRF Token 的名稱、保存方式、忽略某些請求等。 |
sessionManagement() |
用于配置會(huì)話管理,包括定義并發(fā)控制、會(huì)話失效、禁用URL重定向、會(huì)話固定保護(hù)等。 |
rememberMe() |
用于配置Remember-Me 功能,包括定義Remember-Me 令牌的名稱、有效期、加密方法、登錄成功后的處理方式等。 |
exceptionHandling() |
用于配置自定義的異常處理,包括定義異常處理器和異常處理頁面等。 |
headers() |
用于配置HTTP響應(yīng)頭信息,包括定義X-Content-Type-Options、X-XSS-Protection、Strict-Transport-Security 等頭信息。 |
cors() |
用于配置跨域資源共享,包括定義可訪問的來源、Headers 等。 |
addFilter() |
用于向當(dāng)前HttpSecurity 中添加自定義的Filter 。 |
and() |
用于在配置中添加另一個(gè)安全規(guī)則,并將兩個(gè)規(guī)則合并。 |
匹配規(guī)則:
-
URL匹配
方法 | 說明 |
---|---|
requestMatchers() |
配置一個(gè)request Mather 數(shù)組,參數(shù)為RequestMatcher 對象,其match 規(guī)則自定義,需要的時(shí)候放在最前面,對需要匹配的的規(guī)則進(jìn)行自定義與過濾 |
authorizeRequests() |
URL權(quán)限配置 |
antMatchers() |
配置一個(gè)request Mather 的string 數(shù)組,參數(shù)為ant 路徑格式, 直接匹配url
|
anyRequest() |
匹配任意url ,無參 ,最好放在最后面 |
-
保護(hù)URL
方法 | 說明 |
---|---|
authenticated() |
保護(hù)Url ,需要用戶登錄 |
permitAll() |
指定URL無需保護(hù),一般應(yīng)用與靜態(tài)資源文件 |
hasRole(String role) |
限制單個(gè)角色訪問 |
hasAnyRole(String… roles) |
允許多個(gè)角色訪問 |
access(String attribute) |
該方法使用 SPEL , 所以可以創(chuàng)建復(fù)雜的限制 |
hasIpAddress(String ipaddressExpression) |
限制IP 地址或子網(wǎng) |
-
登錄formLogin
方法 | 說明 |
---|---|
loginPage() |
設(shè)置登錄頁面的 URL |
defaultSuccessUrl() |
設(shè)置登錄成功后的默認(rèn)跳轉(zhuǎn)頁面 |
failuerHandler() |
登錄失敗之后的處理器 |
successHandler() |
登錄成功之后的處理器 |
failuerUrl() |
登錄失敗之后系統(tǒng)轉(zhuǎn)向的url ,默認(rèn)是this.loginPage + “?error”
|
loginProcessingUrl() |
設(shè)置登錄請求的 URL,即表單提交的 URL |
usernameParameter() |
設(shè)置登錄表單中用戶名字段的參數(shù)名,默認(rèn)為 username
|
passwordParameter() |
設(shè)置登錄表單中密碼字段的參數(shù)名,默認(rèn)為 password
|
-
登出logout
方法 | 說明 |
---|---|
logoutUrl() |
登出url , 默認(rèn)是/logout l |
logoutSuccessUrl() |
登出成功后跳轉(zhuǎn)的 url 默認(rèn)是/login?logout
|
logoutSuccessHandler() |
登出成功處理器,設(shè)置后會(huì)把logoutSuccessUrl 置為null |
二、用戶認(rèn)證
2.1 導(dǎo)入依賴與配置
基于Spring Initializr
創(chuàng)建SpringBoot
項(xiàng)目(本次案例采用Spring Boot 2.7.12版本為例),導(dǎo)入基本依賴:
<!--Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!--MYSQL 依賴-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
spring-boot-starter-security
包含了以下幾個(gè)主要的依賴:
-
spring-security-core:
Spring Security
的核心模塊,提供了基于權(quán)限的訪問控制以及其他安全相關(guān)功能。 -
spring-security-config:提供了
Spring Security
的配置實(shí)現(xiàn),例如通過Java配置創(chuàng)建安全策略和配置Token存儲等。 -
spring-security-web:提供了
Spring Security Web
的基本功能,例如Servlet
集成和通過HttpSecurity
配置應(yīng)用程序安全策略。
配置application.yml
文件:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/bookshop?useUnicode=true&characterEncoding=utf8&useSSL=false
freemarker:
enabled: true
suffix: .ftl
template-loader-path: classpath:/templates/
mybatis-plus:
# Mybatis Mapper所對應(yīng)的XML位置
mapper-locations: classpath:mapper/*.xml
# 別名包掃描路徑
type-aliases-package: com.ycxw.springsecurity.entity
# 是否開啟自動(dòng)駝峰命名規(guī)則(camel case)映射
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted # 全局邏輯刪除的實(shí)體字段名
logic-delete-value: 1 # 邏輯已刪除值(默認(rèn)為 1)
logic-not-delete-value: 0 # 邏輯未刪除值(默認(rèn)為 0)
logging:
level:
com.jun.security01.mapper: debug
2.2?用戶對象UserDetails
首先準(zhǔn)備一張用戶表,通過mybatis-plus生成代碼后修改User類
并實(shí)現(xiàn)UserDetails接口
。
package com.ycxw.springsecurity.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用戶信息表(繼承UserDetails類)
*
* @author 云村小威
* @since 2023-12-21
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("sys_user")
@ApiModel(value = "User對象", description = "用戶信息表")
public class User implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
@ApiModelProperty("唯一標(biāo)識")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("用戶賬號")
@TableField("username")
private String username;
@ApiModelProperty("用戶密碼")
@TableField("password")
private String password;
@ApiModelProperty("真實(shí)姓名")
@TableField("real_name")
private String realName;
@ApiModelProperty("身份證號")
@TableField("id_card")
private String idCard;
@ApiModelProperty("性別,男或女")
@TableField("gender")
private String gender;
@ApiModelProperty("家庭住址")
@TableField("address")
private String address;
@ApiModelProperty("聯(lián)系電話")
@TableField("phone")
private String phone;
@ApiModelProperty("創(chuàng)建時(shí)間")
@TableField("create_date")
private LocalDateTime createDate;
/**
* 是否過期
*/
@TableField("account_non_expired")
private boolean accountNonExpired;
/**
* 存放用戶的權(quán)限(不存放在數(shù)據(jù)庫中)
*/
@TableField(exist = false)
private List<GrantedAuthority> authorities;
/**
* 是否鎖定
*/
@TableField("account_non_locked")
private boolean accountNonLocked;
/**
* 是否過期
*/
@TableField("credentials_non_expired")
private boolean credentialsNonExpired;
/**
* 是否啟用
*/
@TableField("enabled")
private boolean enabled;
}
?? ? ? ?實(shí)現(xiàn)UserDatails接口會(huì)重寫它的五個(gè)方法,如該類最后的五個(gè)屬性,除authorities屬性以外,請將其他四個(gè)屬性加入數(shù)據(jù)庫表中(原用戶表未有該字段,通過實(shí)現(xiàn)UserDatails后需要身份驗(yàn)證和授權(quán)則要添加)
? ?UserDetails
是Spring Security框架中的一個(gè)接口,它代表了應(yīng)用程序中的用戶信息。UserDetails
接口定義了一組方法,用于獲取用戶的用戶名、密碼、角色和權(quán)限等信息,以便Spring Security可以使用這些信息進(jìn)行身份驗(yàn)證和授權(quán)。
以下是UserDetails
接口中定義的方法:
-
getUsername()
:獲取用戶的用戶名。 -
getPassword()
:獲取用戶的密碼。 -
getAuthorities()
:獲取用戶的角色和權(quán)限信息。 -
isEnabled()
:判斷用戶是否可用。 -
isAccountNonExpired()
:判斷用戶的賬號是否過期。 -
isAccountNonLocked()
:判斷用戶的賬號是否被鎖定。 -
isCredentialsNonExpired()
:判斷用戶的憑證是否過期。
自定義用戶信息時(shí),可以實(shí)現(xiàn)UserDetails
接口并覆蓋其中的方法來提供自己的用戶信息。
2.3?業(yè)務(wù)對象UserDetailsService
修改UserServiceImpl
并實(shí)現(xiàn)UserDetailsService
,重寫loadUserByUsername(String username)
方法。
package com.ycxw.springsecurity.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycxw.springsecurity.entity.User;
import com.ycxw.springsecurity.mapper.UserMapper;
import com.ycxw.springsecurity.service.IUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 用戶信息表 服務(wù)實(shí)現(xiàn)類
*
* @author 云村小威
* @since 2023-12-21
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根據(jù)用戶名查詢用戶
User user = getOne(new QueryWrapper<User>().eq("username", username));
//判斷用戶是否存在
if (Objects.isNull(user))
throw new UsernameNotFoundException("用戶不存在");
return user;
}
}
? ?UserDetailsService
是Spring Security中的一個(gè)接口,它用于從特定數(shù)據(jù)源(如數(shù)據(jù)庫)中獲取用戶詳細(xì)信息,以進(jìn)行身份驗(yàn)證和授權(quán)。實(shí)現(xiàn)該接口的類需要實(shí)現(xiàn)loadUserByUsername
方法,該方法根據(jù)給定的用戶名返回一個(gè)UserDetails
對象,該對象包含有關(guān)用戶的詳細(xì)信息,例如密碼、角色和權(quán)限等。在Spring Security中,UserDetailsService
通常與DaoAuthenticationProvider
一起使用,后者是一個(gè)身份驗(yàn)證提供程序,用于驗(yàn)證用戶的憑據(jù)。
2.4 SecurityConfig配置
2.4.1?BCryptPasswordEncoder密碼編碼器
Spring Security提供了多種密碼加密方式,大致可以歸類于以下幾種:
-
對密碼進(jìn)行明文處理,即不采用任何加密方式;
-
采用MD5加密方式;
-
采用哈希算法加密方式;
BCryptPasswordEncoder
是Spring Security
中一種基于bcrypt
算法的密碼加密方式。bcrypt
算法是一種密碼哈希函數(shù),具有防止彩虹表攻擊的優(yōu)點(diǎn),因此安全性較高。
????????使用BCryptPasswordEncoder
進(jìn)行密碼加密時(shí),可以指定一個(gè)隨機(jī)生成的salt
值(俗稱:加鹽),將其與原始密碼一起進(jìn)行哈希計(jì)算。salt值可以增加密碼的安全性,因?yàn)榧词箖蓚€(gè)用戶使用相同的密碼,由于使用不同的salt
值進(jìn)行哈希計(jì)算,得到的哈希值也是不同的。
在Spring Security
中,可以通過在SecurityConfig
配置類中添加以下代碼來使用BCryptPasswordEncoder
進(jìn)行密碼加密:
@Bean
public PasswordEncoder passwordEncoder() {
? ?return new BCryptPasswordEncoder();
}
這樣就可以在Spring Security中使用BCryptPasswordEncoder
進(jìn)行密碼加密了。
相比BCryptPasswordEncoder密碼編碼器之下明文和MD5加密的缺點(diǎn)是?
明文的缺點(diǎn):
- 安全性低:?明文存儲密碼非常不安全,因?yàn)槿魏斡袡?quán)訪問數(shù)據(jù)庫的人都能夠看到用戶的密碼。
- 容易受到攻擊:?明文存儲密碼很容易受到攻擊,例如暴力破解攻擊和彩虹表攻擊。
MD5 加密的缺點(diǎn):
- 安全性低:?MD5 算法是一種弱加密算法,很容易被破解。
- 不可逆:?MD5 加密是不可逆的,這意味著無法從哈希值中恢復(fù)明文密碼。
- 容易受到碰撞攻擊:?MD5 算法容易受到碰撞攻擊,這意味著可以找到兩個(gè)不同的輸入,它們產(chǎn)生相同的哈希值。(可根據(jù)相同加密后的密碼找出明文密碼)
因此,明文和 MD5 加密都不適合用于保護(hù)用戶密碼。
2.4.2?RememberMe 記住登錄信息
????????在實(shí)際開發(fā)中,為了用戶登錄方便常常會(huì)啟用記住我(Remember-Me
)功能。如果用戶登錄時(shí)勾選了“記住我”選項(xiàng),那么在一段有效時(shí)間內(nèi),會(huì)默認(rèn)自動(dòng)登錄,免去再次輸入用戶名、密碼等登錄操作。該功能的實(shí)現(xiàn)機(jī)理是根據(jù)用戶登錄信息生成 Token
并保存在用戶瀏覽器的 Cookie
中,當(dāng)用戶需要再次登錄時(shí),自動(dòng)實(shí)現(xiàn)校驗(yàn)并建立登錄態(tài)的一種機(jī)制。
Spring Security
提供了兩種 Remember-Me
的實(shí)現(xiàn)方式:
-
簡單加密
Token
:用散列算法加密用戶必要的登錄系信息并生成Token
令牌。 -
持久化
Token
:數(shù)據(jù)庫等持久性數(shù)據(jù)存儲機(jī)制用的持久化Token
令牌。
rememberMe
主要方法介紹:
方法 | 說明 |
---|---|
rememberMeParameter() |
指定在登錄時(shí)“記住我”的 HTTP 參數(shù),默認(rèn)為 remember-me
|
tokenValiditySeconds() |
設(shè)置 Token 有效期為 200s,默認(rèn)時(shí)長為 2 星期 |
tokenRepository() |
指定 rememberMe 的 token 存儲方式,可以使用默認(rèn)的 PersistentTokenRepository 或自定義的實(shí)現(xiàn) |
userDetailsService() |
指定 UserDetailsService 對象 |
rememberMeCookieName() |
指定 rememberMe 的 cookie 名稱 |
基于持久化Token配置:
? ?Remember-Me
功能的開啟需要在configure(HttpSecurity http)
方法中通過http.rememberMe()
配置,該配置主要會(huì)在過濾器鏈中添加 RememberMeAuthenticationFilter
過濾器,通過該過濾器實(shí)現(xiàn)自動(dòng)登錄。
// 注入用戶服務(wù)
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ObjectMapper objectMapper;
// 注入數(shù)據(jù)源(spring自帶)
@Resource
public DataSource dataSource;
// 創(chuàng)建持久令牌存儲庫
@Bean
public PersistentTokenRepository persistentTokenRepository() {
// 創(chuàng)建一個(gè)JdbcTokenRepositoryImpl實(shí)例
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
// 設(shè)置數(shù)據(jù)源
tokenRepository.setDataSource(dataSource);
// 設(shè)置啟動(dòng)時(shí)創(chuàng)建表
tokenRepository.setCreateTableOnStartup(false);
// 返回tokenRepository
return tokenRepository;
}
/*
* 安全過濾器鏈
* */
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
// 設(shè)置角色權(quán)限
//.antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
//其他所有請求都需要用戶進(jìn)行身份驗(yàn)證。
.anyRequest().authenticated()
.and().formLogin()
// 設(shè)置登錄頁面的 URL
.loginPage("/")
// 設(shè)置登錄請求的 URL,即表單提交的 URL
.loginProcessingUrl("/userLogin")
// 設(shè)置登錄表單中用戶名字段的參數(shù)名,默認(rèn)為username
.usernameParameter("username")
// 設(shè)置登錄表單中密碼字段的參數(shù)名,默認(rèn)為password
.passwordParameter("password")
// 登錄成功后返回的數(shù)據(jù)
.successHandler((res, resp, ex) -> {
Object user = ex.getPrincipal();
objectMapper
.writeValue(resp.getOutputStream(), JsonResponseBody.success(user));
})
.and()
/*配置注銷*/
.logout()
// 設(shè)置安全退出的URL路徑
.logoutUrl("/logout")
// 設(shè)置退出成功后跳轉(zhuǎn)的路徑
.logoutSuccessUrl("/").and()
/*配置 rememberMe 功能*/
.rememberMe()
// 指定 rememberMe 的參數(shù)名,用于在表單中攜帶 rememberMe 的值。
.rememberMeParameter("remember-me")
// 指定 rememberMe 的有效期,單位為秒,默認(rèn)2周。
.tokenValiditySeconds(60)
// 指定 rememberMe 的 cookie 名稱。
.rememberMeCookieName("remember-me-cookie")
// 指定 rememberMe 的 token 存儲方式,可以使用默認(rèn)的 PersistentTokenRepository 或自定義的實(shí)現(xiàn)。
.tokenRepository(persistentTokenRepository())
// 指定 rememberMe 的認(rèn)證方式,需要實(shí)現(xiàn) UserDetailsService 接口,并在其中查詢用戶信息。
.userDetailsService(userDetailsService)
return http.build();
}
2.4.3?CSRF防御(跨站請求偽造)
CSRF
(Cross-Site Request Forgery
,跨站請求偽造)是一種利用用戶已登錄的身份在用戶不知情的情況下發(fā)送惡意請求的攻擊方式。攻擊者可以通過構(gòu)造惡意鏈接或者偽造表單提交等方式,讓用戶在不知情的情況下執(zhí)行某些操作,例如修改密碼、轉(zhuǎn)賬、發(fā)表評論等。
????????為了防范CSRF
攻擊,常見的做法是在請求中添加一個(gè)CSRF Token
(也叫做同步令牌、防偽標(biāo)志),并在服務(wù)器端進(jìn)行驗(yàn)證。CSRF Token
是一個(gè)隨機(jī)生成的字符串,每次請求都會(huì)隨著請求一起發(fā)送到服務(wù)器端,服務(wù)器端會(huì)對這個(gè)Token
進(jìn)行驗(yàn)證,如果Token
不正確,則拒絕執(zhí)行請求。
在
Spring Security
中,防范CSRF
攻擊可以通過啟用CSRF
保護(hù)來實(shí)現(xiàn)。啟用CSRF
保護(hù)后,Spring Security
會(huì)自動(dòng)在每個(gè)表單中添加一個(gè)隱藏的CSRF Token
字段,并在服務(wù)器端進(jìn)行驗(yàn)證。如果Token
驗(yàn)證失敗,則會(huì)拋出異常,從而拒絕執(zhí)行請求。啟用CSRF
保護(hù)的方式是在Spring Security
配置文件中添加.csrf()
方法,例如:http ? .csrf() ? ? ? .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
在上面的配置中,我們使用了
CookieCsrfTokenRepository
作為CSRF Token
的存儲方式,并設(shè)置了httpOnly
為false
,以便在客戶端可以訪問到該Token
。????????
在表單中添加:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
.csrf()
主要方法介紹:
方法 | 說明 |
---|---|
disable() |
關(guān)閉CSRF 防御 |
csrfTokenRepository() |
設(shè)置CookieCsrfTokenRepository 實(shí)例,用于存儲和檢索CSRF 令牌。與HttpSessionCsrfTokenRepository 不同,CookieCsrfTokenRepository 將CSRF 令牌存儲在cookie 中,而不是在會(huì)話中。 |
ignoringAntMatchers() |
設(shè)置一組Ant模式,用于忽略某些請求的CSRF 保護(hù)。例如,如果您想要忽略所有以/api/ 開頭的請求,可以使用.ignoringAntMatchers("/api/**") 。 |
csrfTokenManager() |
設(shè)置CsrfTokenManager 實(shí)例,用于管理CSRF 令牌的生成和驗(yàn)證。默認(rèn)情況下,Spring Security 使用DefaultCsrfTokenManager 實(shí)例來生成和驗(yàn)證CSRF 令牌。 |
requireCsrfProtectionMatcher() |
設(shè)置RequestMatcher 實(shí)例,用于確定哪些請求需要進(jìn)行CSRF 保護(hù)。默認(rèn)情況下,Spring Security 將對所有非GET、HEAD、OPTIONS和TRACE 請求進(jìn)行CSRF 保護(hù)。 |
如果針對一些特定的請求接口,不需要進(jìn)行
CSRF
防御,可以通過以下配置忽略:http.csrf().ignoringAntMatchers("/upload"); // 禁用/upload接口的CSRF防御
三、用戶授權(quán)
3.1 授權(quán)介紹
Spring Security 中的授權(quán)分為兩種類型:
-
基于角色的授權(quán):以用戶所屬角色為基礎(chǔ)進(jìn)行授權(quán),如管理員、普通用戶等,通過為用戶分配角色來控制其對資源的訪問權(quán)限。
-
基于資源的授權(quán):以資源為基礎(chǔ)進(jìn)行授權(quán),如 URL、方法等,通過定義資源所需的權(quán)限,來控制對該資源的訪問權(quán)限。
????????Spring Security 提供了多種實(shí)現(xiàn)授權(quán)的機(jī)制,最常用的是使用基于注解的方式,建立起訪問資源和權(quán)限之間的映射關(guān)系。
其中最常用的兩個(gè)注解是 @Secured
和 @PreAuthorize
。@Secured
注解是更早的注解,基于角色的授權(quán)比較適用,@PreAuthorize
基于 SpEL
表達(dá)式的方式,可靈活定義所需的權(quán)限,通常用于基于資源的授權(quán)。
?3.2?構(gòu)建 UserDetails 對象
3.2.1 準(zhǔn)備數(shù)據(jù)表
- sys_user - 用戶信息表
- sys_role - 角色信息表
- sys_user_role - 用戶角色表
- sys_module - 模塊信息表
- sys_role_module - 角色權(quán)限表
3.2.2? 設(shè)置用戶權(quán)限
package com.ycxw.springsecurity.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycxw.springsecurity.entity.*;
import com.ycxw.springsecurity.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private IUserService userService;
@Autowired
private IUserRoleService userRoleService;
@Autowired
private IRoleService roleService;
@Autowired
private IRoleModuleService roleModuleService;
@Autowired
private IModuleService moduleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*查詢當(dāng)前用戶*/
User user = userService
.getOne(new QueryWrapper<User>().eq("username", username));
if (user == null) {
throw new UsernameNotFoundException("用戶名無效");
}
/*
* map 遍歷所有的對象,返回新的數(shù)據(jù)會(huì)放到一個(gè)新流中
* collect 將流中的元素變成一個(gè)集合
* */
//先查詢出所有的身份id
List<Integer> role_ids = userRoleService
.list(new QueryWrapper<UserRole>().eq("user_id", user.getId()))
.stream().map(UserRole::getRoleId)
.collect(Collectors.toList());
//查詢角色對應(yīng)的權(quán)限
List<String> roles = roleService.list(new QueryWrapper<Role>().in("role_id", role_ids))
.stream().map(Role::getRoleName)
.collect(Collectors.toList());
// 查詢權(quán)限對應(yīng)的模塊
List<Integer> module_ids = roleModuleService.list(new QueryWrapper<RoleModule>().in("role_id", role_ids))
.stream().map(RoleModule::getModuleId)
.collect(Collectors.toList());
/// 查詢模塊對應(yīng)的 URL
List<String> modules = moduleService.list(new QueryWrapper<Module>().in("id", module_ids))
.stream().map(Module::getUrl)
/* filter 過濾流中的內(nèi)容(對象不為空)*/
.filter(Objects::nonNull)
.collect(Collectors.toList());
/*
* roles -> [管理員,普通用戶]
* +
* modules -> [book:manager:add,book:manager:list]
*/
// 將角色和模塊合并為一個(gè)集合
roles.addAll(modules);
// roles [管理員,普通用戶,book:manager:add,book:manager:list]
// 構(gòu)建 SimpleGrantedAuthority 對象
List<SimpleGrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 設(shè)置用戶的權(quán)限
user.setAuthorities(authorities);
// 返回 UserDetails 對象
return user;
}
}
?根據(jù)用戶名查詢用戶信息并構(gòu)建 UserDetails 對象,以便 Spring Security 進(jìn)行身份驗(yàn)證。
3.3?修改SpringSecurity配置類
????????當(dāng)我們想要開啟spring
方法級安全時(shí),只需要在任何 @Configuration
實(shí)例上使用@EnableGlobalMethodSecurity
注解就能達(dá)到此目的。同時(shí)這個(gè)注解為我們提供了prePostEnabled
、securedEnabled
和 jsr250Enabled
三種不同的機(jī)制來實(shí)現(xiàn)同一種功能。 ?
?
package com.ycxw.springsecurity.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycxw.springsecurity.resp.JsonResponseBody;
import com.ycxw.springsecurity.resp.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity //前置權(quán)限驗(yàn)證
@EnableGlobalMethodSecurity(prePostEnabled = true) //后置權(quán)限驗(yàn)證
public class WebSecurityConfig {
// 注入數(shù)據(jù)源(spring自帶)
@Resource
public DataSource dataSource;
@Autowired
private ObjectMapper objectMapper;
/*自定義處理身份驗(yàn)證失敗的接口*/
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
// 注入用戶服務(wù)
@Autowired
private UserDetailsService userDetailsService;
/*
* 密碼編碼器: 用于對密碼進(jìn)行加密
* */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 創(chuàng)建持久令牌存儲庫
@Bean
public PersistentTokenRepository persistentTokenRepository() {
// 創(chuàng)建一個(gè)JdbcTokenRepositoryImpl實(shí)例
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
// 設(shè)置數(shù)據(jù)源
tokenRepository.setDataSource(dataSource);
// 設(shè)置啟動(dòng)時(shí)創(chuàng)建表
tokenRepository.setCreateTableOnStartup(false);
// 返回tokenRepository
return tokenRepository;
}
// 創(chuàng)建認(rèn)證管理器
@Bean
public AuthenticationManager authenticationManager() throws Exception {
// 創(chuàng)建一個(gè)DAO認(rèn)證提供者
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 設(shè)置用戶詳情服務(wù)和密碼編碼器
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
// 返回一個(gè)提供者管理器
return new ProviderManager(provider);
}
/*
* 安全過濾器鏈
* */
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
//其他所有請求都需要用戶進(jìn)行身份驗(yàn)證。
.anyRequest().authenticated()
.and().formLogin()
// 設(shè)置登錄頁面的 URL
.loginPage("/")
// 設(shè)置登錄請求的 URL,即表單提交的 URL
.loginProcessingUrl("/userLogin")
// 設(shè)置登錄表單中用戶名字段的參數(shù)名,默認(rèn)為username
.usernameParameter("username")
// 設(shè)置登錄表單中密碼字段的參數(shù)名,默認(rèn)為password
.passwordParameter("password")
// 登錄成功后返回的數(shù)據(jù)
.successHandler((res, resp, ex) -> {
Object user = ex.getPrincipal();
objectMapper
.writeValue(resp.getOutputStream(), JsonResponseBody.success(user));
})
/*登錄失敗后的處理器*/
.failureHandler(myAuthenticationFailureHandler)
.and()
.exceptionHandling()
//權(quán)限不足
.accessDeniedHandler((req, resp, ex) -> {
objectMapper
.writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_ACCESS));
})
//沒有認(rèn)證
.authenticationEntryPoint((req, resp, ex) -> {
objectMapper
.writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_LOGIN));
})
.and()
/*配置注銷*/
.logout()
// 設(shè)置安全退出的URL路徑
.logoutUrl("/logout")
// 設(shè)置退出成功后跳轉(zhuǎn)的路徑
.logoutSuccessUrl("/").and()
/*配置 rememberMe 功能*/
.rememberMe()
// 指定 rememberMe 的參數(shù)名,用于在表單中攜帶 rememberMe 的值。
.rememberMeParameter("remember-me")
// 指定 rememberMe 的有效期,單位為秒,默認(rèn)2周。
.tokenValiditySeconds(60)
// 指定 rememberMe 的 cookie 名稱。
.rememberMeCookieName("remember-me-cookie")
// 指定 rememberMe 的 token 存儲方式,可以使用默認(rèn)的 PersistentTokenRepository 或自定義的實(shí)現(xiàn)。
.tokenRepository(persistentTokenRepository())
// 指定 rememberMe 的認(rèn)證方式,需要實(shí)現(xiàn) UserDetailsService 接口,并在其中查詢用戶信息。
.userDetailsService(userDetailsService).and()
/*使用`POST`請求退出登陸,并攜帶`CRSF`令牌*/
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll().and()
/*CSRF防御配置*/
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
?
@EnableGlobalMethodSecurity
是Spring Security提供的一個(gè)注解,用于啟用方法級別的安全性。它可以在任何@Configuration類上使用,以啟用Spring Security的方法級別的安全性功能。它接受一個(gè)或多個(gè)參數(shù),用于指定要使用的安全注解類型和其他選項(xiàng)。以下是一些常用的參數(shù):
prePostEnabled
:如果設(shè)置為true
,則啟用@PreAuthorize
和@PostAuthorize
注解。默認(rèn)值為false
。
securedEnabled
:如果設(shè)置為true
,則啟用@Secured
注解。默認(rèn)值為false
。
jsr250Enabled
:如果設(shè)置為true
,則啟用@RolesAllowed
注解。默認(rèn)值為false
。
proxyTargetClass
:如果設(shè)置為true
,則使用CGLIB代理而不是標(biāo)準(zhǔn)的JDK動(dòng)態(tài)代理。默認(rèn)值為false
。使用
@EnableGlobalMethodSecurity
注解后,可以在應(yīng)用程序中使用Spring Security提供的各種注解來保護(hù)方法,例如@Secured
、@PreAuthorize
、@PostAuthorize
和@RolesAllowed
。這些注解允許您在方法級別上定義安全規(guī)則,以控制哪些用戶可以訪問哪些方法。注解介紹:
注解 說明 @PreAuthorize
用于在方法執(zhí)行之前對訪問進(jìn)行權(quán)限驗(yàn)證 @PostAuthorize
用于在方法執(zhí)行之后對返回結(jié)果進(jìn)行權(quán)限驗(yàn)證 @Secured
用于在方法執(zhí)行之前對訪問進(jìn)行權(quán)限驗(yàn)證 @RolesAllowed
是Java標(biāo)準(zhǔn)的注解之一,用于在方法執(zhí)行之前對訪問進(jìn)行權(quán)限驗(yàn)證
3.4?控制Controller層接口權(quán)限
package com.ycxw.springsecurity.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@RequestMapping("/")
public String toLogin() {
return "login";
}
@RequestMapping("/index")
public String toIndex() {
return "index";
}
@ResponseBody
@RequestMapping("/order_add")
@PreAuthorize("hasAuthority('order:manager:list')") /*設(shè)置權(quán)限字段*/
public String order_add() {
return "訂單列表";
}
@ResponseBody
@PreAuthorize("hasAuthority('book:manager:add')")
@RequestMapping("/book_add")
public String book_add() {
return "書本新增";
}
}
?
3.5 相關(guān)頁面模版與工具類
1、login.ftl
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>用戶登錄</h1>
<form action="/userLogin" method="post">
<#--添加 CSRF(跨站請求偽造)令牌-->
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<p>
<label>用戶:<input type="text" name="username"/></label>
</p>
<p>
<label>密碼:<input type="password" name="password"/></label>
</p>
<input type="checkbox" name="remember-me"/>記住我<br/>
<input type="submit" value="登錄"/>
</form>
</body>
</html>
2、自定義數(shù)據(jù)返回類
例:
JSON 響應(yīng)的狀態(tài)碼和狀態(tài)信息:
package com.ycxw.springsecurity.resp; import lombok.Getter; @Getter public enum JsonResponseStatus { OK(200, "OK"), UN_KNOWN(500, "未知錯(cuò)誤"), RESULT_EMPTY(1000, "查詢結(jié)果為空"), NO_ACCESS(3001, "沒有權(quán)限"), NO_LOGIN(4001, "沒有登錄"), LOGIN_FAILURE(5001, "登錄失敗"), ; private final Integer code; private final String msg; JsonResponseStatus(Integer code, String msg) { this.code = code; this.msg = msg; } }
3、處理身份驗(yàn)證失敗封類
package com.ycxw.springsecurity.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycxw.springsecurity.entity.User;
import com.ycxw.springsecurity.resp.JsonResponseBody;
import com.ycxw.springsecurity.resp.JsonResponseStatus;
import com.ycxw.springsecurity.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
實(shí)現(xiàn)處理身份驗(yàn)證失敗的接口
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private IUserService userService;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
/*
利用鎖:
判斷當(dāng)前用戶登錄超過3次進(jìn)行鎖定
*/
if (1 == 2) {
User user = userService.getOne(new QueryWrapper<User>().eq("username", request.getParameter("username")));
user.setAccountNonLocked(false);
userService.updateById(user);
}
objectMapper.writeValue(response.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));
}
}
3.6 權(quán)限測試
1、普通用戶權(quán)限
只擁有兩個(gè)路徑的權(quán)限?
測試接口:沒有book_add權(quán)限將不能訪問
?
?
2、管理員權(quán)限
擁有六個(gè)路徑的權(quán)限?
測試接口:管理員能訪問所有接口
?文章來源:http://www.zghlxwxcb.cn/news/detail-761750.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-761750.html
到了這里,關(guān)于SpringSecurity安全框架 ——認(rèn)證與授權(quán)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!