實戰(zhàn)!Shiro安全框架(附詳細(xì)代碼)
前言
關(guān)于 Java 安全框架,一個是Spring Security,一個是Shiro。這兩個框架都是很不錯的,沒有絕對誰好誰壞,看業(yè)務(wù)場景選擇框架,最適合的才是最好的。
今天我們主要來聊聊Shiro這個安全框架,我相信你們也是經(jīng)常用到。至于為什么會有很多人選擇使用Shiro,我認(rèn)為在眾多權(quán)限框架中,Shiro因其簡單而又不失強(qiáng)大的特點引起了不少開發(fā)者的注意。
Apache Shiro是一個強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗證、授權(quán)、密碼和會話管理。使用Shiro的易于理解的API。您可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序。
總結(jié)一點,就是因為Shiro簡單、靈活、易上手。至于那些基本概念,我覺得也很簡單,在代碼中一步一步來進(jìn)行說明。
正文
shiro 實現(xiàn)登錄、認(rèn)證、授權(quán)的流程大概如下:

springboot集成Shiro框架實現(xiàn)按鈕級別的權(quán)限。涉及權(quán)限,這里面就涉及到用戶、角色、權(quán)限三張表和用戶角色、角色權(quán)限兩張關(guān)聯(lián)表。數(shù)據(jù)庫我用的是常見的MYSQL,這里我簡單設(shè)計了一下表的結(jié)構(gòu),如下。
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int(11) NOT NULL,
`available` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`parent_ids` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL,
`resource_type` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', '0', '用戶管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO `sys_permission` VALUES ('2', '0', '用戶添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO `sys_permission` VALUES ('3', '0', '用戶刪除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`available` int(11) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '0', '管理員', 'admin');
INSERT INTO `sys_role` VALUES ('2', '0', 'VIP會員', 'vip');
INSERT INTO `sys_role` VALUES ('3', '1', '測試人員', 'test');
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`permission_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
KEY `FKomxrs8a388bknvhjokh440waq` (`permission_id`),
KEY `FK9q28ewrhntqeipl1t04kh1be7` (`role_id`),
CONSTRAINT `FK9q28ewrhntqeipl1t04kh1be7` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`),
CONSTRAINT `FKomxrs8a388bknvhjokh440waq` FOREIGN KEY (`permission_id`) REFERENCES `sys_permission` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` VALUES ('2', '2');
INSERT INTO `sys_role_permission` VALUES ('3', '2');
INSERT INTO `sys_role_permission` VALUES ('2', '3');
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`uid` int(11) NOT NULL,
`username` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`state` int(1) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '管理員', '123456', '8d78869f470951332959580424d4bf4f', '0');
INSERT INTO `sys_user` VALUES ('2', 'jiangwang', 'vip', '123456', '8d78869f470951332959580424d4bf4f', '0');
INSERT INTO `sys_user` VALUES ('3', 'test', '測試', '123456', '8d78869f470951332959580424d4bf4f', '0');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`role_id` int(11) DEFAULT NULL,
`uid` int(11) DEFAULT NULL,
KEY `FKhh52n8vd4ny9ff4x9fb8v65qx` (`role_id`),
KEY `FKgkmyslkrfeyn9ukmolvek8b8f` (`uid`),
CONSTRAINT `FKgkmyslkrfeyn9ukmolvek8b8f` FOREIGN KEY (`uid`) REFERENCES `sys_user` (`uid`),
CONSTRAINT `FKhh52n8vd4ny9ff4x9fb8v65qx` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
INSERT INTO `sys_user_role` VALUES ('2', '2');
INSERT INTO `sys_user_role` VALUES ('1', '3');
INSERT INTO `sys_user_role` VALUES ('3', '1');
復(fù)制代碼數(shù)據(jù)庫設(shè)計好后,下面就是寫代碼,業(yè)務(wù)邏輯很簡單,用戶登錄成功后,會根據(jù)用戶自身角色而顯示擁有的權(quán)限。登錄要經(jīng)過認(rèn)證,認(rèn)證通過后
創(chuàng)建項目
目錄結(jié)構(gòu)如下:

創(chuàng)建好項目,就需要添加依賴,我使用mybatis框架作為持久層,逆向工程來生成代碼。
添加依賴
<dependencies>
<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>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<overwrite>true</overwrite>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
復(fù)制代碼基本的代碼生成后,就進(jìn)行業(yè)務(wù)代碼的編寫了。
application.properties
server.port=7777
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro_demo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.jw.model
mybatis.mapper-locations=classpath:mapping/*.xml
logging.level.tk.mybatis=TRACE
復(fù)制代碼創(chuàng)建UserService.java文件
@Service
public class UserService
{
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysRolePermissionMapper sysRolePermissionMapper;
@Autowired
private SysPermissionMapper sysPermissionMapper;
public List<SysUser> getList(int id)
{
SysUserExample example = new SysUserExample();
example.createCriteria().andUidEqualTo(id);
return sysUserMapper.selectByExample(example);
}
/**
* 根據(jù)用戶名查詢用戶
*
* @param username 用戶名
* @return 用戶
*/
public SysUser findByUsername(String username)
{
SysUser user = new SysUser();
SysUserExample example = new SysUserExample();
example.createCriteria().andUsernameEqualTo(username);
List<SysUser> userList = sysUserMapper.selectByExample(example);
if (userList.isEmpty())
{
return null;
}
for (SysUser tbUser : userList)
{
user = tbUser;
}
return user;
}
/**
* 查詢用戶的角色
*
* @param id 用戶id
* @return 用戶的角色
*/
public List<SysRole> findRolesById(int id)
{
SysUser userInfo = sysUserMapper.selectByPrimaryKey(id);
if (userInfo == null)
{
throw new RuntimeException("該用戶不存在");
}
List<SysRole> roles = new ArrayList<>();
SysUserRoleExample userRoleExample = new SysUserRoleExample();
userRoleExample.createCriteria().andUidEqualTo(userInfo.getUid());
List<SysUserRole> sysUserRoleList = sysUserRoleMapper.selectByExample(userRoleExample);
List<Integer> rids = new ArrayList<>();
if (!CollectionUtils.isEmpty(sysUserRoleList))
{
for (SysUserRole sysUserRole : sysUserRoleList)
{
rids.add(sysUserRole.getRoleId());
}
if (!CollectionUtils.isEmpty(rids))
{
for (Integer rid : rids)
{
SysRole sysRole = sysRoleMapper.selectByPrimaryKey(rid);
if (sysRole != null)
{
roles.add(sysRole);
}
}
}
}
return roles;
}
/**
* 查詢用戶的權(quán)限
*
* @param roles 用戶的角色
* @return 用戶的權(quán)限
*/
public List<SysPermission> findPermissionByRoles(List<SysRole> roles)
{
List<SysPermission> permissions = new ArrayList<>();
if (!CollectionUtils.isEmpty(roles))
{
Set<Integer> permissionIds = new HashSet<>();//存放菜單id
List<SysRolePermission> sysRolePermissions;
for (SysRole role : roles)
{
SysRolePermissionExample sysRolePermissionExample = new SysRolePermissionExample();
sysRolePermissionExample.createCriteria().andRoleIdEqualTo(role.getId());
sysRolePermissions = sysRolePermissionMapper.selectByExample(sysRolePermissionExample);
if (!CollectionUtils.isEmpty(sysRolePermissions))
{
for (SysRolePermission sysRolePermission : sysRolePermissions)
{
permissionIds.add(sysRolePermission.getPermissionId());
}
}
}
if (!CollectionUtils.isEmpty(permissionIds))
{
for (Integer permissionId : permissionIds)
{
SysPermission permission = sysPermissionMapper.selectByPrimaryKey(permissionId);
if (permission != null)
{
permissions.add(permission);
}
}
}
}
return permissions;
}
}
復(fù)制代碼創(chuàng)建UserController.java文件
@Controller
@RequestMapping("/userInfo")
public class UserController
{
@GetMapping("/userList")
public String getUserList()
{
return "userList";
}
@GetMapping("/userAdd")
public String addUser()
{
return "addUser";
}
@GetMapping("/userDel")
public String deleteUser()
{
return "deleteUser";
}
}
復(fù)制代碼創(chuàng)建LoginController.java文件
@Controller
public class LoginController
{
@GetMapping(value = "/toLogin")
public String toLogin()
{
return "login";
}
@PostMapping("/login")
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model)
{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try
{
subject.login(token);
return "index";
}
catch (UnknownAccountException uae)
{
model.addAttribute("msg", "用戶不存在");
return "login";
}
catch (IncorrectCredentialsException ice)
{
model.addAttribute("msg", "密碼不正確");
return "login";
}
}
@GetMapping("/logOut")
public String logOut()
{
return "login";
}
@GetMapping("/noAuthorization")
public String noAuthorization()
{
return "未經(jīng)授權(quán),無法訪問此頁面";
}
}
復(fù)制代碼創(chuàng)建CurrentUser.java文件
@Data
public class CurrentUser
{
//當(dāng)前登錄用戶
private SysUser userInfo;
//當(dāng)前用戶所擁有的角色
private List<SysRole> roles;
//當(dāng)前用戶所擁有得權(quán)限
private List<SysPermission> permissions;
}
復(fù)制代碼創(chuàng)建MyRealm.java文件
繼承 Shirot 框架的 AuthorizingRealm 類,并實現(xiàn)默認(rèn)的兩個方法:
public class MyRealm extends AuthorizingRealm
{
@Autowired
private UserService userService;
/**
* 授權(quán)
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
{
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//獲取當(dāng)前用戶
CurrentUser currentUser = (CurrentUser) SecurityUtils.getSubject().getPrincipal();
List<SysRole> roles = currentUser.getRoles();
List<SysPermission> permissions = currentUser.getPermissions();
if (!CollectionUtils.isEmpty(roles))
{
for (SysRole role : roles)
{
//授權(quán)角色
authorizationInfo.addRole(role.getRole());
}
}
if (!CollectionUtils.isEmpty(permissions))
{
for (SysPermission permission : permissions)
{
//授權(quán)權(quán)限
authorizationInfo.addStringPermission(permission.getPermission());
}
}
return authorizationInfo;
}
/**
* 認(rèn)證
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
//當(dāng)前用戶名
String username = (String) token.getPrincipal();
SysUser user = userService.findByUsername(username);
if (user == null)
{
return null;
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
//將當(dāng)前用戶的信息放入session中
session.setAttribute("user", user);
//獲取當(dāng)前用戶的角色
List<SysRole> roles = userService.findRolesById(user.getUid());
//獲取當(dāng)前用戶所擁有的權(quán)限
List<SysPermission> permissions = userService.findPermissionByRoles(roles);
CurrentUser currentUser = new CurrentUser();
currentUser.setUserInfo(user);
currentUser.setRoles(roles);
currentUser.setPermissions(permissions);
return new SimpleAuthenticationInfo(currentUser, user.getPassword(), getName());
}
}
復(fù)制代碼創(chuàng)建ShiroConfig.java文件
@Configuration
public class ShiroConfig
{
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
{
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
HashMap<String, String> filterMap = new LinkedHashMap<>();
//授權(quán)
filterMap.put("/userInfo/userAdd", "perms[userInfo:add]");
filterMap.put("/userInfo/userDel", "perms[userInfo:del]");
filterMap.put("/userInfo/userList", "perms[userInfo:view]");
//需要攔截的url
filterMap.put("/userInfo/*", "authc");
//不需要攔截的頁面
filterMap.put("/static/**", "anon");
//被攔截的頁面跳轉(zhuǎn)到登錄頁面
bean.setLoginUrl("/toLogin");
//登錄成功后跳轉(zhuǎn)的鏈接
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/noAuthorization");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager()
{
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myRealm());
return defaultWebSecurityManager;
}
@Bean
public MyRealm myRealm()
{
return new MyRealm();
}
@Bean
public ShiroDialect getShiroDialect()
{
return new ShiroDialect();
}
}
復(fù)制代碼在resources目錄下創(chuàng)建templates文件夾,在該文件夾下創(chuàng)建下列
html文件。
創(chuàng)建index.html文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<h1>首頁</h1>
<div shiro:hasPermission="userInfo:view">
<a th:href="@{/userInfo/userList}">查詢用戶</a>
</div>
<div shiro:hasPermission="userInfo:add">
<a th:href="@{/userInfo/userAdd}">添加用戶</a>
</div>
<div shiro:hasPermission="userInfo:del">
<a th:href="@{/userInfo/userDel}">刪除用戶</a>
</div>
</body>
</html>
復(fù)制代碼創(chuàng)建login.html文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登錄</h1>
<hr>
<p th:text="${msg}"></p>
<form action="/login" method="post">
<p>用戶名:<input type="text" name="username"/></p>
<p>密 碼:<input type="text" name="password"/></p>
<p><input type="submit"/></p>
</form>
</body>
</html>
復(fù)制代碼創(chuàng)建userList.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查詢用戶</title>
</head>
<body>
<p>用戶查詢</p>
</body>
</html>
復(fù)制代碼創(chuàng)建addUser.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用戶</title>
</head>
<body>
<p>添加用戶</p>
</body>
</html>
復(fù)制代碼創(chuàng)建deleteUser.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>刪除用戶</title>
</head>
<body>
<p>刪除用戶</p>
</body>
</html>
復(fù)制代碼啟動項目
訪問
http://localhost:7777/toLogin,登錄頁面,輸入用戶名和密碼,點擊提交。

可以看出,admin用戶有查看和添加權(quán)限,沒有刪除權(quán)限

使用其他用戶登錄,看看有啥權(quán)限。

可以看出,jiangwang用戶擁有查看、添加、刪除權(quán)限。

Shiro加密
我們在數(shù)據(jù)庫中保存的密碼都是明文的,一旦數(shù)據(jù)庫數(shù)據(jù)泄露,那就會造成不可估算的損失,所以我們通常都會使用非對稱加密,簡單理解也就是不可逆的加密,而 md5 加密算法就是符合這樣的一種算法。為了更加安全,我們采用加鹽 + 多次加密的方法。
/**
* 密碼加密
* @param source 密碼
* @param salt 鹽
* @return
*/
public static String md5Encryption(String source, String salt)
{
String algorithmName = "MD5";//加密算法
int hashIterations = 1024;//加密次數(shù)
SimpleHash simpleHash = new SimpleHash(algorithmName, source, salt, hashIterations);
return simpleHash + "";
}
復(fù)制代碼小結(jié)
權(quán)限在我們的項目中應(yīng)用是非常廣泛的,涉及到權(quán)限可以包括:登錄權(quán)限、菜單權(quán)限、數(shù)據(jù)權(quán)限(按鈕權(quán)限),上面的demo可以看出不同的用戶登錄進(jìn)來,有不同的權(quán)限(數(shù)據(jù)權(quán)限),我們的項目中,涉及到權(quán)限都會有這幾個表,用戶表,角色表,權(quán)限表,用戶和角色是多對多的關(guān)系,角色和權(quán)限也是多對多的關(guān)系。
完整代碼已托管碼云:gitee.com/jiangwang00…
作者:初念初戀
鏈接:https://juejin.cn/post/7012506053917016072
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
