介绍
Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
本教程只介绍基本的Shiro使用,不会过多分析源码等,重在使用。
Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。这不就是我们想要的嘛,而且Shiro的API也是非常简单;其基本功能点如下图所示:
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro:不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
接下来我们分别从外部和内部来看看Shiro的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的API,且API契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。
首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:
可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
实战
创建一个springboot项目,数据库创建五张表,这是sql脚本,集成mybatis,可以看我的博客https://blog.csdn.net/jiang_wang01/article/details/110174473
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');
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
2、代码实现
- 创建UserService.java文件
@Service
public class UserService {
@Autowired
SysUserMapper sysUserMapper;
@Autowired
SysUserRoleMapper sysUserRoleMapper;
@Autowired
SysRoleMapper sysRoleMapper;
@Autowired
SysRolePermissionMapper sysRolePermissionMapper;
@Autowired
SysPermissionMapper sysPermissionMapper;
public List<SysUser> getList(int id){
SysUserExample example = new SysUserExample();
example.createCriteria().andUidEqualTo(id);
return sysUserMapper.selectByExample(example);
}
/**
* 根据用户名查询用户
* @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;
}
/**
* 查询用户的权限
* @param roles 用户的角色
* @return 用户的权限
*/
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;
}
}
- 创建UserController.java文件
@Controller
@RequestMapping("/userInfo")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/userList")
public String getUserList(){
return "userList";
}
@GetMapping("/userAdd")
public String addUser(){
return "addUser";
}
@GetMapping("/userDel")
public String deleteUser(){
return "deleteUser";
}
@GetMapping("/getList")
public List<SysUser> getList(int id){
return userService.getList(id);
}
@GetMapping("/findByUsername")
public SysUser findByUsername(@RequestParam("username") String username){
return userService.findByUsername(username);
}
}
- 创建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 "未经授权,无法访问此页面";
}
}
- 创建CurrentUser.java文件
public class CurrentUser {
//当前登录用户
private SysUser userInfo;
//当前用户所拥有的角色
private List<SysRole> roles;
//当前用户所拥有得权限
private List<SysPermission> permissions;
public SysUser getUserInfo() {
return userInfo;
}
public void setUserInfo(SysUser userInfo) {
this.userInfo = userInfo;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
public List<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
}
}
- 创建MyRealm.java文件
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取当前用户
CurrentUser currentUser = (CurrentUser) SecurityUtils.getSubject().getPrincipal();
List<SysRole> roles = currentUser.getRoles();
List<SysPermission> permissions = currentUser.getPermissions();
if(!CollectionUtils.isEmpty(roles)){
for (SysRole role : roles) {
//授权角色
authorizationInfo.addRole(role.getRole());
}
}
if(!CollectionUtils.isEmpty(permissions)){
for (SysPermission permission : permissions) {
//授权权限
authorizationInfo.addStringPermission(permission.getPermission());
}
}
return authorizationInfo;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//当前用户名
String username = (String) token.getPrincipal();
SysUser user = userService.findByUsername(username);
if(user == null){
return null;
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
//将当前用户的信息放入session中
session.setAttribute("user",user);
//获取当前用户的角色
List<SysRole> roles = userService.findRolesById(user.getUid());
//获取当前用户所拥有的权限
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(),ByteSource.Util.bytes(user.getSalt()),getName());
}
}
- 创建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<>();
//授权
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");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterMap.put("/logOut","logout");
//被拦截的页面跳转到登录页面
bean.setLoginUrl("/toLogin");
//登录成功后跳转的链接
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();
}
}
3、编写Html页面
在resources下创建templates文件夹,在该文件夹下创建下列文件
- 创建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>
- 创建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>
- 创建userList.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询用户</title>
</head>
<body>
<p>用户查询</p>
</body>
</html>
- 创建addUser.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<p>添加用户</p>
</body>
</html>
- 创建deleteUser.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>删除用户</title>
</head>
<body>
<p>删除用户</p>
</body>
</html>
4、启动项目
访问页面http://localhost:8085/toLogin
,登录页面,输入用户名和密码,点击提交
- 可以看出,admin用户有查看和添加权限,没有删除权限
- 使用其他用户登录,看看有啥权限
- 可以看出,jiangwang用户拥有查看、添加、删除权限
小结
权限在我们的项目中应用是非常广泛的,涉及到权限可以包括:登录权限、菜单权限、数据权限(按钮权限),上面的demo可以看出不同的用户登录进来,有不同的按钮权限,我们的项目中,涉及到权限都会有这几个表,用户表,角色表,权限表,用户和角色是多对多的关系,角色和权限也是多对多的关系。