前言
关于 Java 安全框架,一个是Spring Security,一个是Shiro。这两个框架都是很不错的,没有绝对谁好谁坏,看业务场景选择框架,最适合的才是最好的。
今天我们主要来聊聊Shiro这个安全框架,我相信你们也是经常用到。至于为什么会有很多人选择使用Shiro,我认为在众多权限框架中,Shiro因其简单而又不失强大的特点引起了不少开发者的注意。
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API。您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
总结一点,就是因为Shiro简单、灵活、易上手。至于那些基本概念,我觉得也很简单,在代码中一步一步来进行说明。
正文
shiro 实现登录、认证、授权的流程大概如下:
springboot集成Shiro框架实现按钮级别的权限。涉及权限,这里面就涉及到用户、角色、权限三张表和用户角色、角色权限两张关联表。数据库我用的是常见的MYSQL,这里我简单设计了一下表的结构,如下。
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');
数据库设计好后,下面就是写代码,业务逻辑很简单,用户登录成功后,会根据用户自身角色而显示拥有的权限。登录要经过认证,认证通过后
创建项目
目录结构如下:
创建好项目,就需要添加依赖,我使用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>
基本的代码生成后,就进行业务代码的编写了。
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
创建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);
}
/**
* 根据用户名查询用户
*
* @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
{
@GetMapping("/userList")
public String getUserList()
{
return "userList";
}
@GetMapping("/userAdd")
public String addUser()
{
return "addUser";
}
@GetMapping("/userDel")
public String deleteUser()
{
return "deleteUser";
}
}
创建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文件
@Data
public class CurrentUser
{
//当前登录用户
private SysUser userInfo;
//当前用户所拥有的角色
private List<SysRole> roles;
//当前用户所拥有得权限
private List<SysPermission> permissions;
}
创建MyRealm.java文件
继承 Shirot 框架的 AuthorizingRealm 类,并实现默认的两个方法:
public class MyRealm extends AuthorizingRealm
{
@Autowired
private 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(), 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");
//被拦截的页面跳转到登录页面
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();
}
}
在resources目录下创建templates文件夹,在该文件夹下创建下列html文件。
创建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>
启动项目
- 访问http://localhost:7777/toLogin,登录页面,输入用户名和密码,点击提交。
- 可以看出,admin用户有查看和添加权限,没有删除权限
- 使用其他用户登录,看看有啥权限。
- 可以看出,jiangwang用户拥有查看、添加、删除权限。
Shiro加密
我们在数据库中保存的密码都是明文的,一旦数据库数据泄露,那就会造成不可估算的损失,所以我们通常都会使用非对称加密,简单理解也就是不可逆的加密,而 md5 加密算法就是符合这样的一种算法。为了更加安全,我们采用加盐 + 多次加密的方法。
/**
* 密码加密
* @param source 密码
* @param salt 盐
* @return
*/
public static String md5Encryption(String source, String salt)
{
String algorithmName = "MD5";//加密算法
int hashIterations = 1024;//加密次数
SimpleHash simpleHash = new SimpleHash(algorithmName, source, salt, hashIterations);
return simpleHash + "";
}
小结
权限在我们的项目中应用是非常广泛的,涉及到权限可以包括:登录权限、菜单权限、数据权限(按钮权限),上面的demo可以看出不同的用户登录进来,有不同的权限(数据权限),我们的项目中,涉及到权限都会有这几个表,用户表,角色表,权限表,用户和角色是多对多的关系,角色和权限也是多对多的关系。
网友评论