SpringBoot - MyBatis Plus - Thymeleaf - Shiro
SpringBoot - MyBatis Plus - Thymeleaf - Shiro
1.許可權的管理
1.1 什麼是許可權管理
基本上涉及到使用者參與的系統都要進行許可權管理,許可權管理屬於系統安全的範疇,許可權管理實現對使用者訪問系統的控制
,按照安全規則或者安全策略控制使用者可以訪問而且只能訪問自己被授權的資源。
許可權管理包括使用者身份認證
和授權
兩部分,簡稱認證授權
。對於需要訪問控制的資源使用者首先經過身份認證,認證通過後使用者具有該資源的訪問許可權方可訪問。
1.2 什麼是身份認證
身份認證
,就是判斷一個使用者是否為合法使用者的處理過程。最常用的簡單身份認證方式是系統通過核對使用者輸入的使用者名稱和口令,看其是否與系統中儲存的該使用者的使用者名稱和口令一致,來判斷使用者身份是否正確。對於採用
1.3 什麼是授權
授權,即訪問控制
,控制誰能訪問哪些資源。主體進行身份認證後需要分配許可權方可訪問系統的資源,對於某些資源沒有許可權是無法訪問的
2.什麼是shiro
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Shiro 是一個功能強大且易於使用的Java安全框架,它執行身份驗證、授權、加密和會話管理。使用Shiro易於理解的API,您可以快速輕鬆地保護任何應用程式—從最小的移動應用程式到最大的web和企業應用程式。
Shiro是apache旗下一個開源框架,它將軟體系統的安全認證相關的功能抽取出來,實現使用者身份認證,許可權授權、加密、會話管理等功能,組成了一個通用的安全認證框架。
3.shiro的核心架構
3.1 Subject
Subject即主體
,外部應用與subject進行互動,subject記錄了當前操作使用者,將使用者的概念理解為當前操作的主體,可能是一個通過瀏覽器請求的使用者,也可能是一個執行的程式。 Subject在shiro中是一個介面,介面中定義了很多認證授相關的方法,外部程式通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權
3.2 SecurityManager
SecurityManager即安全管理器
,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。
SecurityManager是一個介面,繼承了Authenticator, Authorizer, SessionManager這三個介面。
3.3 Authenticator
Authenticator即認證器
,對使用者身份進行認證,Authenticator是一個介面,shiro提供ModularRealmAuthenticator實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,也可以自定義認證器。
3.4 Authorizer
Authorizer即授權器
,使用者通過認證器認證通過,在訪問功能時需要通過授權器判斷使用者是否有此功能的操作許可權。
3.5 Realm
Realm即領域
,相當於datasource資料來源,securityManager進行安全認證需要通過Realm獲取使用者許可權資料,比如:如果使用者身份資料在資料庫那麼realm就需要從資料庫獲取使用者身份資訊。
- 注意:不要把realm理解成只是從資料來源取資料,在realm中還有認證授權校驗的相關的程式碼。
3.6 SessionManager
sessionManager即會話管理
,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分散式應用的會話集中在一點管理,此特性可使它實現單點登入。
3.7 SessionDAO
SessionDAO即會話dao
,是對session會話操作的一套介面,比如要將session儲存到資料庫,可以通過jdbc將會話儲存到資料庫。
3.8 CacheManager
CacheManager即快取管理
,將使用者許可權資料儲存在快取,這樣可以提高效能。
3.9 Cryptography
Cryptography即密碼管理
,shiro提供了一套加密/解密的元件,方便開發。比如提供常用的雜湊、加/解密等功能。
4 SpringBoot環境搭建
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
spring:
datasource:
url: jdbc:mysql://82.156.203.105:3306/shiro?useUnicode=true&characterEncoding=utf-8
username: root
password: ma1234
driver-class-name: com.mysql.cj.jdbc.Driver
thymeleaf:
cache: false
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
啟動,測試web環境
5 mysql資料庫
create database shiro;
use shiro;
create table user(
id int primary key auto_increment not null ,
username varchar(32) not null ,
password varchar(32) not null ,
salt varchar(32) not null
)engine = innodb charset =utf8;
insert into user(id, username, password, salt) VALUES (1,'xiaoma','ad3b0ac5dc49be58d6da7e3869c90c5a','LJFO3@#LKJ4K');
alter table user add perms varchar(32);
@Test
void contextLoads() {
SimpleHash md5 = new SimpleHash("MD5", "123", "LJFO3@#LKJ4K", 1024);
System.out.println(md5);
}
6 ShiroConfig
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("/login","anon");
map.put("/index", "anon");
map.put("/testThymeleaf","authc");
map.put("/add","perms[user:add]");
map.put("/update","perms[user:update]");
// map.put("/**","authc");
shiroFilterFactoryBean.setLoginUrl("/toLoginPage");
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(mysqlRealm());
return securityManager;
}
@Bean
public MysqlRealm mysqlRealm(){
MysqlRealm mysqlRealm = new MysqlRealm();
mysqlRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return mysqlRealm;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
}
package com.mjoe.shiro.realm;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mjoe.shiro.mapper.UserMapper;
import com.mjoe.shiro.pojo.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @ClassName : MysqlRealm
* @Description :
* @Author : MJoeBoyae
* @Date: 2021-07-08 15:46
*/
public class MysqlRealm extends AuthorizingRealm {
@Autowired
UserMapper userMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行授權邏輯");
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",primaryPrincipal);
User user = userMapper.selectOne(queryWrapper);
if (user != null){
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addStringPermission(user.getPerms());
return authorizationInfo;
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("執行認證邏輯");
String principal = (String) authenticationToken.getPrincipal();
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",principal);
User user = userMapper.selectOne(queryWrapper);
if (user != null){
if (principal.equals(user.getUsername())){
return new SimpleAuthenticationInfo(principal,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
}
return null;
}
}
7 Controller
@Controller
public class TestController {
@RequestMapping("/toLoginPage")
public String toLoginPage(){
return "login";
}
@RequestMapping("/testThymeleaf")
public String testThymeleaf(Model model){
model.addAttribute("test","thymeleaf");
return "test";
}
@RequestMapping("/add")
public String add(){
return "/user/add";
}
@RequestMapping("/update")
public String update(){
return "/user/update";
}
@RequestMapping("/login")
public String login(String username,String password, Model model){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "redirect:/testThymeleaf";
}catch (UnknownAccountException e){
model.addAttribute("msg", "使用者名稱不存在");
return "/login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密碼錯誤");
return "/login";
}
}
@RequestMapping("/noAuth")
public String noAuth(){
return "noAuth";
}
}
8 頁面
- index.html
<!DOCTYPE html>
<html lang="en"xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
首頁
<form th:action="@{/toLoginPage}" method="get">
<input type="submit" value="登入">
</form>
</body>
</html>
- test.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<h2 th:text="${test}"></h2>
<a th:href="@{/add}">使用者新增</a>
<a th:href="@{/update}">使用者更新</a>
</body>
</html>
- login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf+VUE+Element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<h1>使用者登入</h1>
<form th:action="@{/login}" method="post">
使用者名稱:<input type="text" name="username" placeholder="請輸入使用者名稱"><br>
密碼:<input type="password" name="password" placeholder="請輸入密碼"><br>
<input type="submit" value="登入">
</form>
</body>
</html>
- noAuth
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf+VUE+Element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<h2>未授權,不可訪問</h2>
</body>
</html>
- add.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf+VUE+Element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<h2>add頁面</h2>
<body>
</body>
</html>
- update.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf+VUE+Element</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<h2>update</h2>
</body>
</html>
9 mybatis-plus
一鍵生成