springboot集成shiro
shiro简介
Shiro是Apache旗下的一个开源项目,它是一个非常易用的安全框架,提供了包括认证、授权、加密、会话管理等功能。Shiro属于轻量级框架,相对于Spring Security简单很多,
并没有security那么复杂,很容易上手。(可以绕过简介,先看示例,回过头再看简介内容)
-
主要功能
- 验证用户身份
- 用户访问权限控制
- 支持单点登录(SSO)功能
- 可以响应认证、访问控制,或Session事件
- 支持提供“Remember Me”服务
-
Authentication(认证):用户身份识别,也就是看用户名和密码是否正确。
-
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
-
Session Management(会话管理):特定于用户的会话管理,甚至在非web 应用程序。
-
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
-
Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
-
缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
-
并发:Apache Shiro 支持多线程应用程序的并发特性。
-
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
-
“Run As”:这个功能允许用户在许可的前提下假设另一个用户的身份。
-
“Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录
::: tip 提示
本示例不讲springboot搭建和集成mybatis等基础知识,没有这方面基础的可以学习前面的文章5springboot集成mybatis
:::
数据库设计
我们进行权限控制的时候,一般是希望一个用户可以有一个或者多个角色,一个角色又对应多个权限。我们创建5张表来控制权限
- 用户表
sys_user
- 角色表
sys_role
- 用户角色关系表
sys_user_role
- 权限表
sys_permission
- 角色权限关系表
sys_role_permission
创建表
- 创建用户表
sys_user
并且插入两条数据
CREATE TABLE `sys_user` (
`id` varchar(64) NOT NULL COMMENT '主键id',
`username` varchar(100) DEFAULT NULL COMMENT '登录账号',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`salt` varchar(100) DEFAULT NULL COMMENT '电子邮件',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
INSERT INTO `sys_user` VALUES ('user01', 'test01', '111', '2');
INSERT INTO `sys_user` VALUES ('user02', 'test02', '111', '2');
- 创建角色表
sys_role
并且插入1条数据
CREATE TABLE `sys_role` (
`id` varchar(64) NOT NULL,
`role` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_role` VALUES ('role01', 'admin', '管理员');
- 创建用户角色关系表
sys_user_role
并且插入2条数据
CREATE TABLE `sys_user_role` (
`user_id` varchar(64) DEFAULT NULL,
`role_id` varchar(64) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_user_role` VALUES ('user01', 'role01');
INSERT INTO `sys_user_role` VALUES ('user02', 'role01');
- 创建权限表
sys_permission
并且插入2条数据
CREATE TABLE `sys_permission` (
`id` varchar(64) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`parent_id` varchar(64) DEFAULT NULL,
`resource_type` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_permission` VALUES ('p02', '新增用户', 'p1', 'button', 'sysUser/add', 'sysUser:add');
INSERT INTO `sys_permission` VALUES ('p03', '查询所有用户', 'p1', 'button', 'sysUser/listAll', 'sysUser:listAll');
INSERT INTO `sys_permission` VALUES ('p1', '用户管理', null, 'menu', 'sysUser/list', 'sysUser:list');
- 创建角色权限关系表
sys_role_permission
并且插入3条数据
CREATE TABLE `sys_role_permission` (
`role_id` varchar(64) DEFAULT '',
`permission_id` varchar(64) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_role_permission` VALUES ('role01', 'p1');
INSERT INTO `sys_role_permission` VALUES ('role01', 'p02');
INSERT INTO `sys_role_permission` VALUES ('role01', 'p03');
::: tip 提示
这个表结构和关系只是举例子,大家理解原理后可以根据自己业务需求去创建表结构。如果一个用户只有一个角色的话,就不用用户角色关系表,直接在用户表里面加个role_id就可以了。
:::
添加依赖
在正常的springboot项目中,加入shiro依赖包
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
创建ShiroConfig
添加一个Shiro配置类,主要配置路由的访问控制,以及注入自定义的认证器MyShiroRealm。
@Configuration
@Slf4j
public class ShiroConfig {
/**
* Filter Chain定义说明
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
log.info("**********shiroFilter");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/sysUser/login", "anon");
// 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");
// 没有登录的用户请求需要登录的页面时自动跳转到的页面(或者url),如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/sysUser/unlogin");
// 登录的用户访问了没有被授权的资源自动跳转到的页面(或者url)
shiroFilterFactoryBean.setUnauthorizedUrl("/sysUser/url403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(MyShiroRealm myRealm) {
log.info("**********securityManager");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
}
- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
- filterChainDefinitionMap:url的拦截器,可以指定哪些url是白名单,哪些url需要认证才能访问等
- setLoginUrl:没有登录的用户请求需要登录的页面时自动跳转到的页面(或者url),如果不设置默认会自动寻找Web工程根目录下的”/login.jsp”页面
- setUnauthorizedUrl:登录的用户访问了没有被授权的资源自动跳转到的页面(或者url)
::: tip 提示
shiro的内置过滤器 - anon:无需认证就可以访问 默认
- authc:必须认证了才能访问
- user:必须拥有记住我功能才能访问
- perms:必须拥有对某个的权限才能访问
- role:拥有某个角色权限才能访问
如果不想深究这个,你只是想登录成功后才能访问一些url,那么只使用anon和authc就可以了,anon过滤白名单,authc过滤所有非白名单的url就可以了。
:::
创建MyShiroRealm
添加一个MyShiroRealm并继承AuthorizingRealm,实现其中的两个方法。
- doGetAuthenticationInfo:实现用户认证,通过服务加载用户信息并构造认证对象返回。(用户登录判断,用户名和密码正确才可以登录成功)
- doGetAuthorizationInfo:实现权限认证,通过服务加载用户角色和权限信息设置进去。(登录成功后,用户请求的时候判断用户是否有该url权限)
@Component
@Slf4j
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取用户登录信息
SysUser sysUser = (SysUser)principals.getPrimaryPrincipal();
// 循环添加用户角色信息
for(SysRole sysRole:sysUser.getRoleList()){
authorizationInfo.addRole(sysRole.getRole());
// 循环添加所有权限信息(sysPermission.getPermission()取出来的其实就是一个标识,数据库里面怎么录入的,@RequiresPermissions验证的时候就用什么)
for(SysPermission sysPermission:sysRole.getSysPermissionList()){
authorizationInfo.addStringPermission(sysPermission.getPermission());
}
}
return authorizationInfo;
}
/**
* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
* @param authToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken)
throws AuthenticationException {
log.info("权限配置-->MyShiroRealm.doGetAuthenticationInfo()");
// 获取用户的输入的账号.
String userName = (String)authToken.getPrincipal();
//实际项目中,这里可以根据实际情况做缓存,不用每次登录都查询数据库
SysUser sysUser = sysUserService.getUserByName(userName);
log.info("权限配置-->doGetAuthenticationInfo-->sysUser="+sysUser);
if(sysUser == null){
return null;
}
// 进行认证,将正确数据给shiro处理,密码不用自己比对,shiro会自己处理我们查询出来的sysUser的密码和登录传入的password
/* 第一个参数可以放user对象,
* 第二个参数必须放密码,
* 第三个参数放 当前realm的名字,因为可能有多个realm*/
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
sysUser, //用户信息
sysUser.getPassword(), //密码
getName() //realm name
);
//清除之前的授权信息
super.clearCachedAuthorizationInfo(authenticationInfo.getPrincipals());
// 存入用户对象
SecurityUtils.getSubject().getSession().setAttribute("loginUser", sysUser);
// 返回给安全管理器,securityManager,由securityManager比对数据库查询出的密码和页面提交的密码。如果有问题,向上抛异常,一直抛到控制器
return authenticationInfo;
}
}
创建测试controller
@RestController
@RequestMapping("sysUser")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@RequestMapping("save")
public String save(SysUser sysUser){
sysUserService.save(sysUser);
return "添加成功";
}
/**
* 测试不加@RequiresPermissions的时候,只要登录成功就可以访问
* @return
*/
@RequestMapping("listAll")
public Object listAll(){
return sysUserService.findAll();
}
/**
* 测试必须拥有sysUser:list权限才可以访问,数据库有该权限
* @return
*/
@RequiresPermissions("sysUser:list")
@RequestMapping("list")
public Object list(){
//为了演示,内容与listAll一样
return sysUserService.findAll();
}
/**
* 测试接口,数据库里面没有添加sysUser:list2权限,测试登录成功后,执行该方法,会报一个没有权限的异常
* @return
*/
@RequiresPermissions("sysUser:list2")
@RequestMapping("list2")
public Object list2(){
//为了演示,内容与listAll一样
return sysUserService.findAll();
}
@RequestMapping("login")
public Object login(SysUser sysUserLogin){
Map<String, Object> result = new HashMap<>();
// 不用自己验证,直接使用shiro验证用户名密码
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserLogin.getUsername(), sysUserLogin.getPassword());
try {
// 执行subject.login(token)后会执行MyShiroRealm的AuthenticationInfo进行身份认证
subject.login(token);
//登录成功后,会向前台返回一个sessionid
result.put("token", subject.getSession().getId());
result.put("msg", "登录成功");
} catch (IncorrectCredentialsException e) {
result.put("msg", "密码错误");
} catch (LockedAccountException e) {
result.put("msg", "登录失败,该用户已被冻结");
} catch (AuthenticationException e) {
result.put("msg", "该用户不存在");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 没有登录时返回前台的信息
* @return
*/
@RequestMapping("unlogin")
public Object unlogin(){
Map<String, Object> result = new HashMap<>();
result.put("message","未登录,请您先登录");
return result;
}
/**
* 登录后没有权限时返回前台的信息
* @return
*/
@RequestMapping("url403")
public Object url403(){
Map<String, Object> result = new HashMap<>();
result.put("message","未登录403,请您先登录");
return result;
}
}
- 未登录时
在页面输入:http://localhost:8088/moyundong/sysUser/list
,
会在页面提示{"message":"未登录,请您先登录"}
- 登录后(在页面用
http://localhost:8088/moyundong/sysUser/login?username=test01&password=111
进行登录,一般登录都是post请求,为了方便测试就用了get方式)- 在页面输入:
http://localhost:8088/moyundong/sysUser/list
,会查询出用户信息 - 在页面输入:
http://localhost:8088/moyundong/sysUser/listAll
,会查询出用户信息 - 在页面输入:
http://localhost:8088/moyundong/sysUser/list2
,控制台会报异常org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method
::: tip 提示
- 在页面输入:
- service、dao等代码就不列出来了,有需要的朋友可以在文末下载代码
- 如果不是在浏览器测试,使用的是postman等工具测试的化,先调用登录接口,会返回一个32位的JSESSIONID,其它接口调用的时候必须在headers的cookie里面加上JSESSIONID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这样shiro就会知道该用户是已经登录过的
:::
执行顺序
- 没有登录的时候,非白名单的url请求最终都会跳转到setLoginUrl设置的url
- 登录时执行subject.login(token)后会执行MyShiroRealm的AuthenticationInfo进行身份认证,认证成功后会返回一个32位的JSESSIONID
- 登录后执行没有@RequiresPermissions的请求的时候(cookie里面必须带上32位的JSESSIONID),会直接成功
- 登录后执行有@RequiresPermissions的请求的时候(cookie里面必须带上32位的JSESSIONID),会进入MyShiroRealm的AuthorizationInfo方法进行权限认证
- 在浏览器执行登录成功的时候会默认把JSESSIONID放到headers的cookie里面,其它接口直接调用就可以
- 在测试工具执行登录成功的时候应手动把JSESSIONID放到headers的cookie里面,其它接口才可以调用成功
总结
这个是shiro最基本的用法,算是最基础的入门。我们没有使用缓存,没有对密码进行加密。
本节示例下载地址:java相关demo下载列表
1介绍
2springboot定时任务
3springboot定时任务配置详解
4springboot动态定时任务
5springboot集成websocket
6springboot多数据源
7springboot配置druid监听
8springboot自定义注解
9springboot常见注解详解
10springboot接收参数详解
11springboot验证机制@Valid和@Validated
12springboot集成Swagger2
13springboot集成swagger-bootstrap-ui
14springboot集成shiro
15springboot集成shiro(二)
16springboot集成jwt
17springboot集成ActiveMQ
18springboot缓存机制
🍉🍉🍉 欢迎大家来博客了解更多内容:java乐园 🍉🍉🍉
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13502.html