环境准备
Spring Boot 2.0.4.RELEASE
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.20</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
数据库MySQL
`SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `permission`;CREATE TABLE `permission` (
`pid` int(11) NOT NULL COMMENT '权限id',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL, `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT
NULL, PRIMARY KEY (`pid`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci
ROW_FORMAT = Compact;
INSERT INTO `permission` VALUES (1, 'add', NULL);INSERT INTO `permission` VALUES (2, 'delete', NULL);INSERT INTO `permission` VALUES (3, 'edit', NULL);INSERT INTO `permission` VALUES (4, 'query', NULL);
DROP TABLE IF EXISTS `permission_role`;CREATE TABLE `permission_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rid` int(11) NOT NULL, `pid` int(11) NOT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE =
utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `permission_role` VALUES (1, 1, 1);INSERT INTO `permission_role` VALUES (2, 1, 2);INSERT INTO `permission_role` VALUES (3, 1, 3);INSERT INTO `permission_role` VALUES (4, 1, 4);INSERT INTO `permission_role` VALUES (5, 2, 1);INSERT INTO `permission_role` VALUES (6, 2, 4);
DROP TABLE IF EXISTS `role`;CREATE TABLE `role` ( `rid` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色id', `rname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL, PRIMARY KEY (`rid`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE =
utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `role` VALUES (1, 'admin');INSERT INTO `role` VALUES (2, 'customer');
DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `uid` int(11) NOT NULL COMMENT '用户id', `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL, `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL, PRIMARY KEY (`uid`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci
ROW_FORMAT = Compact;-
NSERT INTO `user` VALUES (1, 'admin', '123');INSERT INTO `user` VALUES (2, 'demo', '123');-
DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL, `rid` int(11) NOT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE =
utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 2, 2);
SET FOREIGN_KEY_CHECKS = 1;
数据库表
[图片上传失败...(image-a16ba8-1558117658470)]
[图片上传失败...(image-d4b9e7-1558117658470)]
[图片上传失败...(image-f03df9-1558117658470)]
[图片上传失败...(image-6f0a88-1558117658470)]
[图片上传失败...(image-89de60-1558117658470)]
配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8
username: root
password: root
mvc:
view:
prefix: /pages/
suffix: .jsp
jpa:
show-sql: true
创建自己的Realm 继承AuthorizingRealm
重写里面的两个方法
授权
doGetAuthorizationInfo(PrincipalCollection principalCollection)
从PrincipalCollection中取出User
创建一个List<String> permissionList = new Arraylist<>();
获取到该用户对应的角色
获取到该角色下所有的权限
将权限名称添加到permissionList里
声明一个SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
将permissionList放入info里 info.addStringPermissions(permissionList);
最后将info返回
认证登陆方法
doGetAuthenticationInfo(AuthenticationToken token)
这个方法需要将我们传入的token转换成UsernamePasswordToken
然后从里面取出对应的username
再从数据库里取出对应的User
转换成SimpleAuthenticationInfo对象
public class AuthReaml extends AuthorizingRealm {
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PermissionRepository permissionRepository;
@Autowired
private PermissionRoleRepository permissionRoleRepository;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();
List<String> permissionList = new ArrayList<>();
List<String> roleNameList = new ArrayList<>();
//根据用户id找到对应的角色
//定义角色集合
Set<Role> roleSet = new HashSet<>();
//从用户角色中间表查找到该用户下的所有角色
List<UserRole> userRoleList = userRoleRepository.findByUid(user.getUid());
//使用lambda取到该用户下所有的rid
List<Integer> rids = userRoleList.stream().map(UserRole::getRid).collect(Collectors.toList());
//获取数据库所有角色
List<Role> roleList = roleRepository.findAll();
for (Integer rid:rids){
for (Role role : roleList){
if (rid == role.getRid()){
//存入角色集合中
roleSet.add(role);
}
}
}
//遍历角色集合
if(CollectionUtils.isNotEmpty(roleSet)){
//如果角色集合不为空,创建权限集合//
Set<Permission> permissionSet = new HashSet<>();
//通过角色id从权限角色中间表获取到所有的权限id
//1.先查询所有的角色权限集合
List<PermissionRole> permissionRoleList = permissionRoleRepository.findAll();
//2.通过判断角色id和权限id 获取该角色对应的所有权限id
//创建一个集合用来存放该角色对应的权限id
List<Integer> permissionIds = new ArrayList<>();
if (CollectionUtils.isNotEmpty(permissionRoleList)){
for (Role role:roleSet){
roleNameList.add(role.getRname());
for (PermissionRole permissionRole:permissionRoleList){
if (role.getRid() == permissionRole.getRid()){
permissionIds.add(permissionRole.getPid());
}
}
}
}
//通过权限id获取到所有的权限数据
//从数据库查找到所有的权限数据
List<Permission> permissions = permissionRepository.findAll();
if(CollectionUtils.isNotEmpty(permissionIds) && CollectionUtils.isNotEmpty(permissions)){
for (Integer permissionid : permissionIds){
for (Permission permission:permissions){
if (permissionid == permission.getPid()){
permissionList.add(permission.getName());
}
}
}
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionList);
info.addRoles(roleNameList);
return info;
}
/** * 认证登陆
* @param token
* @return
* @throws AuthenticationException
*/
@Overrideprotected
AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
User user = userRepository.findByUsername(username);
return new SimpleAuthenticationInfo(user,user.getPassword(),
this.getClass().getName());
}
密码校验功能重写 新建类CredentialMatcher 继承SimpleCredentialsMatcher
public class CredentialMatcher extends SimpleCredentialsMatcher {
/**
* 密码校验规则重写
* @param token
* @param info
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//取出当前用户输入的密码
String password = new String(usernamePasswordToken.getPassword());
//获取数据库中该用户密码
String dbPassword = (String)info.getCredentials();
return this.equals(password,dbPassword);
}
}
创建Shiro配置类 ShiroConfiguration
@Qualifier 表示从spring中取出来的
@Configuration
public class ShiroConfiguration {
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
//定义登陆url
bean.setLoginUrl("/login");
//登陆成功之后的url
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorized");
LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
//必须登陆才可以访问index
filterChainDefinitionMap.put("/index","authc");
//不需要登陆就可以访问login
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/loginUser","anon");
// filterChainDefinitionMap.put("/druid/**","anon"); //配置durid完全放行
filterChainDefinitionMap.put("/admin","roles[admin]"); //具有admin角色的用户才可以访问 /admin接口
filterChainDefinitionMap.put("/edit","perms[edit]"); //具有edit这个权限才可以访问 /edit接口
filterChainDefinitionMap.put("/**","user");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authReaml") AuthReaml authReaml){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authReaml);
return manager;
}
@Bean("authReaml")
public AuthReaml authReaml(@Qualifier("credentialMatcher") CredentialMatcher matcher){
AuthReaml authReaml = new AuthReaml();
authReaml.setCredentialsMatcher(matcher);
return authReaml;
}
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher(){
return new CredentialMatcher();
}
//Shiro跟spring的关联
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
创建Controller
@Controller
public class TestController {
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/logOut")
public String logOut(){
Subject subject = SecurityUtils.getSubject();
if (subject!=null){
subject.logout();
}
return "login";
}
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin success";
}
@RequestMapping("/unauthorized")
public String unauthorized(){
return "unauthorized";
}
@RequestMapping("/edit")
@ResponseBody
public String edit(){
return "edit success";
}
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
User user = (User) subject.getPrincipal();
session.setAttribute("user",user);
return "index";
}catch (Exception e){
return "login";
}
}
}
编写jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head><body>
<h1>欢迎登陆</h1>
<form action="/loginUser" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit" value="提交"></form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home</title>
</head>
<body><h1>欢迎登陆,${user.username}</h1>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>unauthorized</title>
</head>
<body>
Unauthorized!
</body>
</html>
省略repository接口与实体类
总结:
优点:
提供了一套框架,而且这个框架可用,且易于使用
更灵活,应对需求能力强,Web能力强
可以与很多框架和应用进行集成
缺点:
除了自己实现RBAC外,操作的界面也需要自己实现