使用场景:
- 用户登录验证:
在登录界面,需要对用户名和密码进行验证,验证通过后跳转到指定页面。验证不通过提示登录信息错误。
原理 管家服务, 如果shiro有感情,会发生以下事情:
1、 身份认证:那小伙在登录,你把那小伙的身份证给我,辨认、放行、驱赶的事交给我了
2、角色权限: 那小伙已经进大厅了,要去办公室拿东西,你把小伙的工作证给我,我去看那小伙是不是随意乱串呢。
-
目录
1、创建微服务,pom.xml添加引用
2、当有用户登录的时候,将该用户的账号密码,角色、权限准备告诉shiro
3、加shrio好友,把要刚定义的告知信息注入shiro
4、给要限制访问的资源关门上锁
5、枯燥的测试准备工作
6、测试 -
场景模拟:
当看完文档一脸懵的时候,就该动手操作了。简单粗暴的操作完后,再去看文档…****************************** 开始简单粗暴的场景模拟*****************************************
-
第一步 创建微服务,pom.xml添加引用
<!--web依赖,基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!--引入thymeleaf,作为web服务使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入jpa,连接mysql-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql的java连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
-
第二步 当有用户登录的时候,将该用户的账号密码,角色、权限等告诉shiro
注:要注入到框架,shiro也收不到信息
1、继承AuthorizingRealm, 实现这两个方法
doGetAuthenticationInfo(AuthenticationToken token)
翻译:那小伙在登录,他已经给我说了他的身份证信息(参数AuthenticationToken),你把那小伙的身份证复印件给我,辨认、放行、驱赶的事交给我了doGetAuthorizationInfo(PrincipalCollection principalCollection)
翻译:刚才那小伙已经进大厅了,我有他的身份证复印件(参数principalCollection),现在他要去办公室拿东西,你把小伙的工作证给我,我去看那小伙是不是随意乱串呢
doGetAuthenticationInfo(AuthenticationToken token)
该方法主要是用来验证身份和密码的。实现过程很简单,我们只需查询账户密码,交给shiro就行。
账号验证原理:在登陆输入用户名和密码时,shiro已经接管了用户名和密码。在此处只需取出用户名和密码,交给shiro即可,下面几个骚操作是框架自己完成的。不用实现
1、 框架会自动校验账户的正确性。
2、 如果正确,shrio会自动控制页面跳转到成功页面。
3、 如果不正确,shiro会将向/login发一个post请求,里面包含了错误信息,可以定制修改后返回给前端
.
.
doGetAuthorizationInfo(PrincipalCollection principalCollection)
实现方式主要是告诉shiro,当前登录的账号属于什么角色,有哪些权限。
此方法调用 hasRole,hasPermission的时候才会进行回调.
2、上代码,就是这么枯燥
package com.shiro.Service.shiro;
import com.shiro.Service.UserService;
import com.shiro.entity.SysPermission;
import com.shiro.entity.SysRole;
import com.shiro.entity.UserInfo;
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;
/**
* 自定义验证规则
*/
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 此方法调用封装角色和权限信息
* 把当前登录用户的角色信息和权限信息告诉shiro
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("访问权限认证,此处调用了doGetAuthorizationInfo");
SimpleAuthorizationInfo author = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();
/**
* 设置权限和角色信息
*/
for(SysRole role:userInfo.getRoleList()){
author.addRole(role.getRole());
for(SysPermission p:role.getPermissions()){
author.addStringPermission(p.getPermission());
}
}
return author;
}
/**
* 告诉shiro,名称是token的这个用户的账号和密码信息
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException
{
//获取用户的输入的账号
String username = (String)token.getPrincipal();
//根据用户名查询用户基本信息,这个是查询数据库,不想查询只做演示的话此处造点假数据就行了
UserInfo userInfo = userService.findByUsername(username);
if(userInfo == null) {
return null;
}
//根据用户名查询权限信息,并封装在用户信息中,此时未实现
/*
* 获取权限信息:这里没有进行实现,
* 请自行根据UserInfo,Role,Permission进行实现;
* 获取之后可以在前端for循环显示所有链接;
*/
//userInfo.setPermissions(userService.findPermissions(user));
SimpleAuthenticationInfo authInfo = new SimpleAuthenticationInfo(userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),
//salt=username+salt
getName() );
return authInfo;
}
}
- 第三步 加shrio好友,把要刚定义的告知信息注入shiro
粗暴上代码
package com.shiro.config;
import com.shiro.Service.shiro.MyShiroRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public MyShiroRealm myShiroRealm(){
return new MyShiroRealm();
}
@Bean
public SecurityManager sercurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myShiroRealm());
return defaultWebSecurityManager;
}
/**
* 定义账号密码验证时,需要控制的路由跳转
* 验证成功的导向和验证失败的导向
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean filterFactoryBean(SecurityManager securityManager){
/**
* 准备拦截器的拦截路径
* 需要保持拦截顺序,所以用linkedHashMap,一般将/**放在最下面
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//shiro已经内部实现了,不用关注这个页面
filterMap.put("/logout", "logout");
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterMap.put("/**", "authc");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//需要登录的页面
shiroFilterFactoryBean.setLoginUrl("/login");
//登录成功的页面
shiroFilterFactoryBean.setSuccessUrl("/index");
//登录失败的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 访问资源权限的配置
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor source = new AuthorizationAttributeSourceAdvisor();
source.setSecurityManager(securityManager);
return source;
}
}
- 第四步 给角色可访问的资源关门上锁
就是要限制访问的资源上添加注解 @RequiresPermissions(“userInfo:add”)
以上的四步已经完成了技术问题。剩下的就是枯燥的测试过程,如果不想连数据库,可以造假数据直接测
- 1、准备mysql数据库,选个没用的数据库执行下面sql
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` bigint(20) NOT NULL,
`available` bit(1) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`parent_ids` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL,
`resource_type` enum('menu','button') DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', '', '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO `sys_permission` VALUES ('2', '', '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO `sys_permission` VALUES ('3', '', '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL,
`available` bit(1) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '', '管理员', 'admin');
INSERT INTO `sys_role` VALUES ('2', '', 'VIP会员', 'vip');
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`role_id` bigint(20) NOT NULL,
`permission_id` bigint(20) NOT 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 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` VALUES ('1', '2');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`uid` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
KEY `FKhh52n8vd4ny9ff4x9fb8v65qx` (`role_id`),
KEY `FKgkmyslkrfeyn9ukmolvek8b8f` (`uid`),
CONSTRAINT `FKgkmyslkrfeyn9ukmolvek8b8f` FOREIGN KEY (`uid`) REFERENCES `user_info` (`uid`),
CONSTRAINT `FKhh52n8vd4ny9ff4x9fb8v65qx` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`uid` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`state` tinyint(4) NOT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`uid`),
UNIQUE KEY `UK_f2ksd6h8hsjtd57ipfq9myr64` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of user_info
-- ----------------------------
INSERT INTO `user_info` VALUES ('1', '管理员', '123456', 'd3c59d25033dbf980d29554025c23a75', '8', 'admin');
- 2、准备实体类和dao层,就不贴代码了,贴个git
https://gitee.com/tangjunchao/springboot-shiro.git
进去了不用看我的其他仓库,都是私有的。
枯燥结束,开始测试
1、访问http://localhost:8001/userInfo/add 自动跳到了 http://localhost:8001/login
2、输入错误账号或密码有提示
3、登录成功后自动跳到了第一次想访问的页面 http://localhost:8001/userInfo/add
4、访问个没权限的页面,会跳到错误页面