SpringBoot整合SpringSecurity(附源碼)
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
在前幾篇博客里,我們對于SpringBoot框架的項目中的認(rèn)證還是采用最樸素的攔截器來實現(xiàn)的,那SpringBoot這么高級,就沒有什么成熟的解決方案嗎?有的,Spring Security,今天我們就來認(rèn)識Spring Security,再配上一個demo加深理解。
Spring Security簡介
Spring Security 是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認(rèn)的技術(shù)選型,他可以實現(xiàn)強大的Web安全控制,對于安全控制,我們僅需要引入?spring-boot-starter-security?模塊,進(jìn)行少量的配置,即可實現(xiàn)強大的安全管理。
記住常用的幾個類:
WebSecurityConfigurerAdapter:自定義 Security 策略AuthenticationManagerBuilder:自定義認(rèn)證策略@EnableWebSecurity:開啟 WebSecurity 模式
Spring Security的兩個主要目標(biāo)是 “認(rèn)證” 和 “授權(quán)”(訪問控制)。
“認(rèn)證”(Authentication)
身份驗證是關(guān)于驗證您的憑據(jù),如用戶名/用戶ID和密碼,以驗證您的身份。
身份驗證通常通過用戶名和密碼完成,有時與身份驗證因素結(jié)合使用。
“授權(quán)” (Authorization)
授權(quán)發(fā)生在系統(tǒng)成功驗證您的身份后,最終會授予您訪問資源(如信息,文件,數(shù)據(jù)庫,資金,位置,幾乎任何內(nèi)容)的完全權(quán)限。
那實際上除了SpringSecurity,用的比較多的安全框架還有shiro。可以下宏觀的了解一下
SpringSecurity和Shiro的相同點和不同點。
相同點
認(rèn)證功能、授權(quán)功能、加密功能、會話管理、緩存支持、rememberMe功能
不同點
1、SpringSecurity基于Spring開發(fā),項目中如果使用Spring作為基礎(chǔ),配合SpringSecurity做權(quán)限更加方便,而Shiro需要和Spring進(jìn)行整合開發(fā)
2、SpringSecurity功能比Shiro更加豐富些,例如安全防護(hù)
3、SpringSecurity社區(qū)資源比Shiro豐富
4、Shiro配置和使用比較簡單,SpringSecurity上手復(fù)雜
5、Shiro依賴性低,不需要任何框架和容器,可以獨立運行,而SpringSecurity依賴于Spring容器。
測試Demo
前置準(zhǔn)備
首先創(chuàng)建一個SpringBoot項目,勾選SpringSecurity模塊
或者創(chuàng)建項目后導(dǎo)入依賴
???org.springframework.boot
???spring-boot-starter-security
為了方便前端展示,我們還導(dǎo)入thymeleaf依賴
創(chuàng)建幾個前端頁面(用于后面來測試權(quán)限訪問)提取碼:o9dz
項目結(jié)構(gòu)如下圖所示

測試主頁及跳轉(zhuǎn)(測試時先把SpringSecurity依賴注釋掉)

編寫基礎(chǔ)配置類
在項目下創(chuàng)建config包,新建SecurityConfig.java
package?com.feng.config;
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;
/**
?*?springsecurity-test
?*?
?*
?*?@author?:?Nicer_feng
?*?@date?:?2020-10-13?11:38
?**/
@EnableWebSecurity??//開啟WebSecurity模式
public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{
?????//?授權(quán)規(guī)則
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????//?首頁所有人可以訪問
????????//?其他界面只有對應(yīng)的角色(權(quán)限)才可以訪問
????????http.authorizeRequests().antMatchers("/").permitAll()
????????????????.antMatchers("/level1/**").hasRole("vip1")
????????????????.antMatchers("/level2/**").hasRole("vip2")
????????????????.antMatchers("/level3/**").hasRole("vip3");
????}
}
測試
開啟后我們再測試下

可以發(fā)現(xiàn)報了403forbidden錯誤
There?was?an?unexpected?error?(type=Forbidden,?status=403).
Access?Denied
我們在配置類中添加如未登錄強制跳轉(zhuǎn)到login頁面
這里的http.formLogin();表示開啟自動配置的登錄功能,如果無權(quán)限則跳轉(zhuǎn)到/login
測試發(fā)現(xiàn)確實如此,如果沒有權(quán)限則強制跳轉(zhuǎn)到登錄頁面

但需要注意的是,這個登錄頁面并不是我們自己寫的login頁面,而是SpringSecurity自帶的默認(rèn)登錄頁面
重寫認(rèn)證規(guī)則
我們可以自定義認(rèn)證規(guī)則,重寫configure(AuthenticationManagerBuilder auth)方法來配置認(rèn)證的規(guī)則。

可以看到這里的認(rèn)證規(guī)則常用的有這幾種,這里先用inMemoryAuthentication(內(nèi)存數(shù)據(jù)庫)的來演示
添加配置代碼
package?com.feng.config;
import?org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
/**
?*?springsecurity-test
?*?
?*
?*?@author?:?Nicer_feng
?*?@date?:?2020-10-13?11:38
?**/
@EnableWebSecurity??//開啟WebSecurity模式
public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{
????@Override
????protected?void?configure(AuthenticationManagerBuilder?auth)?throws?Exception?{
????????auth.inMemoryAuthentication()
????????????????.withUser("feng").password("111111").roles("vip1")
????????????????.and()
????????????????.withUser("user").password("22222").roles("vip2")
????????????????.and()
????????????????.withUser("admin").password("000000")
????????????????.roles("vip1","vip2","vip3");
????}
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests().antMatchers("/").permitAll()
????????????????.antMatchers("/level1/**").hasRole("vip1")
????????????????.antMatchers("/level2/**").hasRole("vip2")
????????????????.antMatchers("/level3/**").hasRole("vip3");
????????http.formLogin();
????}
}
我們添加了三個用戶,分別擁有不同的權(quán)限,重啟tomcat后測試

可以發(fā)現(xiàn)這里報了無id映射的錯誤,之所以報這個錯是因為從前端傳過來的密碼需要進(jìn)行加密,否則無法登陸,我們是用官方推薦的bcrypt加密方式
????@Override
????????protected?void?configure(AuthenticationManagerBuilder?auth)?throws?Exception?{
????????????auth.inMemoryAuthentication().passwordEncoder(new?BCryptPasswordEncoder())
????????????????????.withUser("feng").password(new?BCryptPasswordEncoder().encode("111111"))
????????????????????.roles("vip1")
????????????????????.and()
????????????????????.withUser("user").password(new?BCryptPasswordEncoder().encode("222222"))
????????????????????.roles("vip2")
????????????????????.and()
????????????????????.withUser("admin").password(new?BCryptPasswordEncoder().encode("000000"))
????????????????????.roles("vip1","vip2","vip3");
????????}
重啟測試

權(quán)限注銷
在配置類中加入注銷功能
@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????......
????????//開啟自動配置的注銷的功能
????????//?/logout?注銷請求
????????http.logout();
????}
在index頁面添加logout注銷功能

"right?menu">
????
????"item"?th:href="@{/login}">
????????"address?card?icon">?登錄
????
????
????"item"?th:href="@{/logout}">
????????"address?card?icon">?注銷
????
注意這里的默認(rèn)提示中的/login和/logout都是SpringSecurity自帶的默認(rèn)界面
點擊注銷后,會返回登錄界面


如果想要注銷后仍然回到首頁,可以在logout()后添加logoutSuccessUrl
http.logout().logoutSuccessUrl("/");
根據(jù)權(quán)限顯示不同頁面
上面我們設(shè)置了三個用戶,擁有不同權(quán)限,在實際業(yè)務(wù)中,那能不能讓擁有相應(yīng)權(quán)限的用戶只顯示相應(yīng)的界面呢?是可以做到的,我們需要利用thymeleaf 和SpringSecurity結(jié)合的功能
首先添加對應(yīng)依賴
???org.thymeleaf.extras
???thymeleaf-extras-springsecurity5
???3.0.4.RELEASE
修改前端的頁面
頭部命名空間改為
"en"?xmlns:th="http://www.thymeleaf.org"?xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
網(wǎng)頁部分
"right?menu">
????
????"!isAuthenticated()">
????????"item"?th:href="@{/login}">
????????????"address?card?icon">?登錄
????????
????
????
????"isAuthenticated()">
????????"item">
????????????"address?card?icon">
????????????用戶名:"principal.username">
????????????角色:"principal.authorities">
????????
????
????"isAuthenticated()">
????????"item"?th:href="@{/logout}">
????????????"address?card?icon">?注銷
????????
????
然后我們需要給相應(yīng)模塊附上相應(yīng)的權(quán)限等級
"column"?sec:authorize="hasRole('vip1')">
???"ui?raised?segment">
???????"ui">
???????????"content">
???????????????"content">Level?1
???????????????
???????????????
???????????????
???????????????
???????????
???????
???
"column"?sec:authorize="hasRole('vip2')">
???"ui?raised?segment">
???????"ui">
???????????"content">
???????????????"content">Level?2
???????????????
???????????????
???????????????
???????????????
???????????
???????
???
"column"?sec:authorize="hasRole('vip3')">
???"ui?raised?segment">
???????"ui">
???????????"content">
???????????????"content">Level?3
???????????????
???????????????
???????????????
???????????????
???????????
???????
???
測試
再次啟動測試

可以看到我們做到“千人千面”了
rememberMe
在實際登錄中我們肯定有記住密碼這個狀態(tài),那在SpringSecurity中如何配置?十分簡單,只需要在授權(quán)規(guī)則內(nèi)加一行http.rememberMe()即可

重啟測試下發(fā)現(xiàn)登錄頁面多了一個Remember me的選項

打開瀏覽器的開發(fā)者工具發(fā)現(xiàn)該cookies信息保存14天

并且如果我們點擊注銷,該cookies信息自動刪除

定制登錄頁
實際業(yè)務(wù)中顯然不可能使用SpringSecurity自帶的登錄界面,我們需要定制自己的登錄頁面,首先我們要在配置內(nèi)的登錄頁配置后添加指定的loginpage

前端也需要指向我們自定義的登錄請求
???"item"?th:href="@{/toLogin}">
????????????????"address?card?icon">?登錄
???

在login.html配置提交請求需要改成post
并且login.html增加記住我的選項框
type="checkbox"?name="remember">?記住我
加入這個功能時,配置類需要加入
http.rememberMe().rememberMeParameter("remember");
運行測試

發(fā)現(xiàn)登錄都沒問題,但是注銷的時候缺出現(xiàn)了404錯誤,別緊張,是因為它默認(rèn)防止 csrf 跨站請求偽造,因為會產(chǎn)生安全問題,我們可以將請求改為 post 表單提交,或者在 Spring security 中關(guān)閉 csrf 功能。在授權(quán)配置中增加 http.csrf().disable() 即可

再次運行即可
彩蛋
如果我們想用jdbc連數(shù)據(jù)庫來看認(rèn)證用戶呢(用戶信息存在數(shù)據(jù)庫中)
首先在數(shù)據(jù)庫中創(chuàng)建一個用戶信息數(shù)據(jù)庫,這里直接貼sql
/*
SQLyog?Ultimate?v12.09?(64?bit)
MySQL?-?5.7.23?:?Database?-?testspringsecurity
*********************************************************************
*/
/*!40101?SET?NAMES?utf8?*/;
/*!40101?SET?SQL_MODE=''*/;
/*!40014?SET?@OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS,?UNIQUE_CHECKS=0?*/;
/*!40014?SET?@OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS,?FOREIGN_KEY_CHECKS=0?*/;
/*!40101?SET?@OLD_SQL_MODE=@@SQL_MODE,?SQL_MODE='NO_AUTO_VALUE_ON_ZERO'?*/;
/*!40111?SET?@OLD_SQL_NOTES=@@SQL_NOTES,?SQL_NOTES=0?*/;
CREATE?DATABASE?/*!32312?IF?NOT?EXISTS*/`testspringsecurity`?/*!40100?DEFAULT?CHARACTER?SET?utf8?*/;
USE?`testspringsecurity`;
/*Table?structure?for?table?`authorities`?*/
DROP?TABLE?IF?EXISTS?`authorities`;
CREATE?TABLE?`authorities`?(
??`username`?varchar(50)?NOT?NULL,
??`authority`?varchar(50)?NOT?NULL,
??UNIQUE?KEY?`ix_auth_username`?(`username`,`authority`),
??CONSTRAINT?`fk_authorities_users`?FOREIGN?KEY?(`username`)?REFERENCES?`users`?(`username`)
)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8;
/*Data?for?the?table?`authorities`?*/
insert??into?`authorities`(`username`,`authority`)?values?('feng','ROLE_vip1');
/*Table?structure?for?table?`users`?*/
DROP?TABLE?IF?EXISTS?`users`;
CREATE?TABLE?`users`?(
??`username`?varchar(50)?NOT?NULL,
??`password`?varchar(500)?NOT?NULL,
??`enabled`?tinyint(1)?NOT?NULL,
??PRIMARY?KEY?(`username`)
)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8;
/*Data?for?the?table?`users`?*/
insert??into?`users`(`username`,`password`,`enabled`)?values?('feng','$2a$10$cxCFUH0.O.pWGnp9KhC0He2T9jmQx4AV3mcjWMvmqM6eSq/cHfxFG',1);
/*!40101?SET?SQL_MODE=@OLD_SQL_MODE?*/;
/*!40014?SET?FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS?*/;
/*!40014?SET?UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS?*/;
/*!40111?SET?SQL_NOTES=@OLD_SQL_NOTES?*/;
這里插入了一條數(shù)據(jù)肯定會好奇吧,下面在解釋

在剛才的項目中添加mysql、jdbc依賴
????????
????????????org.springframework.boot
????????????spring-boot-starter-jdbc
????????
????????
????????
????????????mysql
????????????mysql-connector-java
????????????runtime
????????
配置類添加鏈接數(shù)據(jù)庫信息
application.properties
spring.thymeleaf.cache=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/testspringsecurity?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=******
我們在配置類中注釋掉inMemoryAuthentication的方法
使用jdbcAuthentication認(rèn)證,改為
package?com.feng.config;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import?javax.sql.DataSource;
/**
?*?springsecurity-test
?*?
?*
?*?@author?:?Nicer_feng
?*?@date?:?2020-10-13?11:38
?**/
@EnableWebSecurity??//開啟WebSecurity模式
public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{
????@Autowired
????private?DataSource?dataSource;
????@Override
????protected?void?configure(AuthenticationManagerBuilder?auth)?throws?Exception?{
//????????auth.inMemoryAuthentication().passwordEncoder(new?BCryptPasswordEncoder())
//????????????????.withUser("feng").password(new?BCryptPasswordEncoder().encode("111111"))
//????????????????.roles("vip1")
//????????????????.and()
//????????????????.withUser("user").password(new?BCryptPasswordEncoder().encode("222222"))
//????????????????.roles("vip2")
//????????????????.and()
//????????????????.withUser("admin").password(new?BCryptPasswordEncoder().encode("000000"))
//????????????????.roles("vip1","vip2","vip3");
????????auth.jdbcAuthentication()
????????????????.dataSource(dataSource)
????????????????.usersByUsernameQuery("select?*?from?users?WHERE?username=?")
????????????????.authoritiesByUsernameQuery("select?*?from?authorities?where?username=?")
????????????????.passwordEncoder(new?BCryptPasswordEncoder());
????}
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests().antMatchers("/").permitAll()
????????????????.antMatchers("/level1/**").hasRole("vip1")
????????????????.antMatchers("/level2/**").hasRole("vip2")
????????????????.antMatchers("/level3/**").hasRole("vip3");
????????http.formLogin().loginPage("/toLogin");
????????http.rememberMe().rememberMeParameter("remember");
????????http.csrf().disable();
????????//開啟自動配置的注銷的功能
????????//?/logout?注銷請求
????????http.logout().logoutSuccessUrl("/");
????}
}
重啟服務(wù)器后啟動項目,上面的一串密碼就是123456通過BCrypt加密后符號,因為不加密不能認(rèn)證
注意數(shù)據(jù)庫的認(rèn)證權(quán)限和用戶密碼信息分開存儲,且用戶權(quán)限前在數(shù)據(jù)庫中要加入**ROLE_**前綴
123456通過BCrypt加密后為
$2a$10$1gLSaAn2i4LSiq28ACalg.jOUeTNBWfSJtt19uEySy2vcUsi7qyBy
啟動后測試(并在數(shù)據(jù)庫中添加賬戶測試登錄)

版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:
https://blog.csdn.net/weixin_43876186/article/details/109054037
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進(jìn)入java1234官方微信群
感謝點贊支持下哈?
