SpringBoot中使用SpringSecurity實現認證和授權(入門)
簡介
SpringSecurity 是Spring專案組中用來提供安全認證(authentication)和授權(authorization)服務的框架。所謂的認證通俗的說就是判斷正在操作的使用者和密碼是否匹配,而授權就是控制使用者能做什麼操作,也就是能幹什麼能看到什麼。
環境搭建
以idea開發工具為例,模板引擎使用的是thymeleaf
pom.xml
<properties>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version >
<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId >
<scope>test</scope>
</dependency>
</dependencies>
複製程式碼
新增一些模板檔案,這些拷貝即可
KungfuController.java
@Controller
public class KungfuController {
private final String PREFIX = "pages/";
/**
* 歡迎頁
* @return
*/
@GetMapping("/")
public String index() {
return "welcome";
}
/**
* 登陸頁
* @return
*/
@GetMapping("/userlogin")
public String loginPage() {
return PREFIX+"login";
}
/**
* level1頁面對映
* @param path
* @return
*/
@GetMapping("/level1/{path}")
public String level1(@PathVariable("path")String path) {
return PREFIX+"level1/"+path;
}
/**
* level2頁面對映
* @param path
* @return
*/
@GetMapping("/level2/{path}")
public String level2(@PathVariable("path")String path) {
return PREFIX+"level2/"+path;
}
/**
* level3頁面對映
* @param path
* @return
*/
@GetMapping("/level3/{path}")
public String level3(@PathVariable("path")String path) {
return PREFIX+"level3/"+path;
}
/**
* 登入失敗頁
* @return
*/
@GetMapping("/loginError")
public String loginError() {
return "loginError";
}
}
複製程式碼
level1/1.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>羅漢拳</h1>
<p>羅漢拳站當央,打起來不要慌</p>
</body>
</html>
複製程式碼
level1/2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>武當長拳</h1>
<p>長一點在長一點</p>
</body>
</html>
複製程式碼
level1/3.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>全真劍法</h1>
<p>全都是真的</p>
</body>
</html>
複製程式碼
level2/1.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>太極拳</h1>
<p>
一個西瓜圓又圓 劈它一刀成兩半 你一半來 給你你不要 給他他不收 那就不給 把兩人攆走 他們不走你走 走啦,一揮手,傷自尊
不買西瓜別纏我,緩慢糾纏様 兩人纏我賴皮,手慢動作左右揮動 看我厲害,轉頭緩步拍蒼蠅狀 拍死了,手抱西瓜狀+奧特曼十字手+廣播操準備運動的站立
</p>
</body>
</html>
複製程式碼
level2/2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>七傷拳</h1>
<p>練這拳的人全都死了</p>
</body>
</html>
複製程式碼
level2/3.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>梯雲縱</h1>
<p>踩自己的腳往上跳</p>
</body>
</html>
複製程式碼
level3/1.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>葵花寶典</h1>
<p>欲練神功,揮刀自宮</p>
</body>
</html>
複製程式碼
level3/2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>龜派氣功</h1>
<p>龜-派-氣-功-波</p>
</body>
</html>
複製程式碼
level3/3.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>獨孤九劍</h1>
<p>欲練此劍,必先犯賤</p>
</body>
</html>
複製程式碼
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">歡迎登陸武林祕籍管理系統</h1>
<hr>
<div align="center">
<form action="" method="post">
使用者名稱:<input name=""/><br>
密碼:<input name=""><br/>
<input type="submit" value="登陸">
</form>
</div>
</body>
</html>
複製程式碼
loginError.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登入錯誤</h1>
</body>
</html>
複製程式碼
welcome.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">歡迎光臨武林祕籍管理系統</h1>
<h2 align="center">遊客您好,如果想檢視武林祕籍 <a th:href="@{/login}">請登入</a></h2>
<hr>
<h3>普通武功祕籍</h3>
<ul>
<li><a th:href="@{/level1/1}">羅漢拳</a></li>
<li><a th:href="@{/level1/2}">武當長拳</a></li>
<li><a th:href="@{/level1/3}">全真劍法</a></li>
</ul>
<h3>高階武功祕籍</h3>
<ul>
<li><a th:href="@{/level2/1}">太極拳</a></li>
<li><a th:href="@{/level2/2}">七傷拳</a></li>
<li><a th:href="@{/level2/3}">梯雲縱</a></li>
</ul>
<h3>絕世武功祕籍</h3>
<ul>
<li><a th:href="@{/level3/1}">葵花寶典</a></li>
<li><a th:href="@{/level3/2}">龜派氣功</a></li>
<li><a th:href="@{/level3/3}">獨孤九劍</a></li>
</ul>
</body>
</html>
複製程式碼
專案的目錄結構如下:
然後我們可以先啟動專案,訪問下專案地址,檢查下環境是否正常:
這個時候我們可以點選上面的每個功夫連結到每個功夫頁面中。記憶體中配置使用者資訊實現許可權與授權
首先引入SpringSecurity的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
複製程式碼
增加一個配置類config/MySecurityConfig.java,需要繼承WebSecurityConfigurerAdapter
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
//定製請求的授權規則
http.authorizeRequests().antMatchers("/").permitAll()//訪問/路徑不需要擁有任何角色
.antMatchers("/level1/**").hasRole("VIP1")//訪問此路徑需要VIP1角色
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
}
}
複製程式碼
這時候我們再重啟,訪問每個需要角色的路徑時就會被拒絕訪問
這時候需要我們新增自動登入的功能(在上面的configure(HttpSecurityhttp)方法中新增),會給我們生成一個自動登入到頁面,傳送指定的請求/login會來到登入頁面,如果登入失敗會重定向到/login?error表示登入失敗http.formLogin();
複製程式碼
如果沒有登入訪問需要許可權的url就會來到這個登入頁面。
具體的登入資訊我們可以在類中定義,也可以在資料庫中定義。
定義認證規則
首先重寫configure(AuthenticationManagerBuilder auth)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//這裡定義的是從記憶體中取出使用者名稱和密碼,後面的是資料庫中取出來驗證
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("VIP1","VIP2")//分別表示登入名,登入密碼,此使用者擁有的角色
.and()
.withUser("lisi").password("123456").roles("VIP2","VIP3")
.and()
.withUser("wangwu").password("123456").roles("VIP1","VIP3");
}
複製程式碼
定義完認證規則之後我們可以重啟專案,再次訪問,這時候訪問需要角色的頁面時會重定向到登入頁面,這時候我們輸入上面定義的任一使用者登入資訊,登入成功後即可訪問對應角色的頁面。
接下來我們可以加上登出功能
MySecurityConfig配置類configure(HttpSecurity http)方法中加入自動登出功能
//登出成功會預設返回/login?logout
http.logout().logoutSuccessUrl("/");//指定登出成功以後來到首頁
//前臺頁面post請求訪問/logout表示使用者登出,並且清空session
複製程式碼
前臺頁面welcome.html加上退出表單
<form th:action="@{/logout}" method="post"><!-- 方法需要時post -->
<input type="submit" value="登出"/>
</form>
複製程式碼
這時候我們再次重啟專案訪問專案地址登入之後就可以登出了,登出之後就不能訪問任何需要角色的頁面了。
接下來再增加認證授權在前臺頁面的應用前臺頁面程式碼如下
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">歡迎光臨武林祕籍管理系統</h1>
<div sec:authorize="!isAuthenticated()"><!-- 如果沒認證 -->
<h2 align="center">遊客您好,如果想檢視武林祕籍 <a th:href="@{/login}">請登入</a></h2>
</div>
<div sec:authorize="isAuthenticated()"><!-- 如果認證認證了 -->
<!-- 這裡可以讀取到登入的使用者名稱和擁有的角色 -->
<h2><span sec:authentication="name"></span>,您好,您的角色有:
<span sec:authentication="principal.authorities"></span></h2>
<form th:action="@{/logout}" method="post"><!-- 方法需要時post -->
<input type="submit" value="登出"/>
</form>
</div>
<hr>
<div sec:authorize="hasRole('VIP1')"><!-- 對擁有VIP1角色的使用者顯示 -->
<h3>普通武功祕籍</h3>
<ul>
<li><a th:href="@{/level1/1}">羅漢拳</a></li>
<li><a th:href="@{/level1/2}">武當長拳</a></li>
<li><a th:href="@{/level1/3}">全真劍法</a></li>
</ul>
</div>
<div sec:authorize="hasRole('VIP2')"><!-- 對擁有VIP2角色的使用者顯示 -->
<h3>高階武功祕籍</h3>
<ul>
<li><a th:href="@{/level2/1}">太極拳</a></li>
<li><a th:href="@{/level2/2}">七傷拳</a></li>
<li><a th:href="@{/level2/3}">梯雲縱</a></li>
</ul>
</div>
<div sec:authorize="hasRole('VIP3')"><!-- 對擁有VIP3角色的使用者顯示 -->
<h3>絕世武功祕籍</h3>
<ul>
<li><a th:href="@{/level3/1}">葵花寶典</a></li>
<li><a th:href="@{/level3/2}">龜派氣功</a></li>
<li><a th:href="@{/level3/3}">獨孤九劍</a></li>
</ul>
</div>
</body>
</html>
複製程式碼
我們還需要在pom檔案中加一個依賴:SpringSecurity和thymeleaf的整合模組
<properties><!-- 在properties控制下版本 -->
<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
</properties
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
複製程式碼
這時候我們再次訪問登入就可以看到頁面上所登入使用者的不同展示的差異了
記住登入狀態此時我們登入之後關閉瀏覽器再次啟動後,訪問專案需要重新登入。如果能夠記住登入狀態,以後只需要登入一次,只要不登出,重啟瀏覽器訪問也依然是登入狀態。
開啟記住我功能
在配置類的configure(HttpSecurity http)方法中新增
http.rememberMe();
複製程式碼
當我們新增上面的配置時,登入頁面就會自動加上一個記住的按鈕。SpringSecurity是將登入資訊儲存到cookie中,預設存在的時間為14天。但是如果點選登出,就會立即清除這個cookie資訊。
指定自己的登入頁面,
- 新增login.html的登入請求/userlogin,並繫結input輸入框
<form th:action="@{/userlogin}" method="post"> 使用者名稱:<input name="user"/><br> 密碼:<input name="pwd"><br/> <input type="checkbox" name="remeber">記住我 <br/> <input type="submit" value="登陸"> </form> 複製程式碼
- 修改預設登入請求
http.formLogin().usernameParameter("user").passwordParameter("pwd")//指定登入input標籤名稱
.loginPage("/userlogin").failureUrl("/loginError");//指定定製的登入請求和登入失敗後的請求
http.rememberMe().rememberMeParameter("remeber");//新增記住標籤的引數名稱
複製程式碼
- 將welcome.html頁面的登入請求更換為/userlogin
<a th:href="@{/userlogin}">請登入</a>
複製程式碼
這時候訪問的時候就會來到自定義的登入頁面
資料庫使用者資訊實現許可權與授權
建立幾張表如下:
CREATE TABLE USER(
user_id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(30),PASSWORD VARCHAR(30)
)
CREATE TABLE role(
role_id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(30)
)
CREATE TABLE user_role(
role_id INT,user_id INT
)
-- 插入資料 這裡使用的是MD5加密演演算法,密碼是123456
INSERT INTO role(NAME)VALUES('ROLE_VIP1');
INSERT INTO role(NAME)VALUES('ROLE_VIP2');
INSERT INTO role(NAME)VALUES('ROLE_VIP3');
INSERT INTO USER(NAME,PASSWORD)VALUES('zhangsan','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO USER(NAME,PASSWORD)VALUES('list',PASSWORD)VALUES('wangwu','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO user_role(role_id,user_id)VALUES(1,1);
INSERT INTO user_role(role_id,user_id)VALUES(2,user_id)VALUES(3,2);
INSERT INTO user_role(role_id,3);
複製程式碼
引入pom檔案
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
複製程式碼
加入application.properties配置資訊
server.port=8085
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
複製程式碼
編寫實體類UserInfo
public class UserInfo implements Serializable {
private Integer userId;
private String name;
private String password;
private List<Role> roles;
//...
}
複製程式碼
編寫實體類Role
public class Role implements Serializable {
private Integer roleId;
private String name;
//...
}
複製程式碼
編寫dao
UserMapper
public interface UserMapper {
@Select("select * from user where name=#{username}")
@Results({
@Result(id = true,property = "userId",column = "user_id"),@Result(property = "name",column = "NAME"),@Result(property = "password",column = "PASSWORD"),@Result(property = "roles",column = "user_id",javaType = java.util.List.class,many = @Many(select = "com.example.mapper.RoleMapper.findRoleByUserId"))
})
UserInfo findByUsername(String username);
}
複製程式碼
RoleMapper
public interface RoleMapper {
@Select("select * from role where role_id in (select role_id from user_role where user_id=#{userId})")
@Results({
@Result(id = true,property = "roleId",column = "role_id"),column = "name")
})
Role findRoleByUserId(Integer userId);
}
複製程式碼
編寫service UserService需要繼承UserDetailsService
public interface UserService extends UserDetailsService {//繼承UserDetailsService
UserInfo findByUsername(String username);
}
複製程式碼
實現類
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserInfo findByUsername(String username) {
return userMapper.findByUsername(username);
}
/**
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userMapper.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(userInfo);
User user = new User(userInfo.getName(),userInfo.getPassword(),getAuthority(userInfo.getRoles()));
return user;
}
public List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> list = new ArrayList<>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority(role.getName()));
}
return list;
}
}
複製程式碼
配置類MySecurityConfig中需要修改configure(AuthenticationManagerBuilder auth)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService())//user Details Service驗證
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {//指定下加密規則
return MD5Utils.encode((String) rawPassword);
}
@Override//匹配接收到的密碼是否和資料庫中查詢到的一致
public boolean matches(CharSequence rawPassword,String encodedPassword) {
return encodedPassword.equals(MD5Utils.encode((String)rawPassword));
}
});
}
複製程式碼
最後加密類
public class MD5Utils {
private static final String SALT = "tamboo";
public static String encode(String password) {
password = password + SALT;
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
char[] charArray = password.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
}
複製程式碼
現在可以訪問專案地址,登入的密碼是123456
第一次寫部落格,水平有限,思路也有問題,語言組織的也有問題,希望諒解