shiro 概述及整合springboot案例(一)
shiro权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
shiro权限模型
shiro权限控制的方案
1. 基于角色的权限控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
if(主体.hasRole(“总经理角色id”)){ 查询工资}
2. 基于资源的权限控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,
访问控制流程如下:if(主体.isPermission(“查询工资的权限”)){ 查询工资}
shiro简介
1. 什么是shiro
Shiro是Apache旗下一个开源的java安全框架,它将软件系统安全认证相关的功能抽取出来,实现用户身份认证,授权、加密、会话管理、缓存等功能,组成了一个通用的安全认证框架.。
2. shiro的优势
- 简单性:Shiro 在使用上较 Spring Security 更简单,更容易理解。
- 灵活性:Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
- Web支持:Shiro 拥有一流的Web应用程序的支持,使您能够创建基于应用程序URL和网络协议(如REST)灵活的安全策略,同时还提供了一组JSP库来控制页面输出。
- 可插拔:Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。
3. shiro的功能
- Authentication: 认证,验证用户是不是拥有相应的身份
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web 支持,可以非常容易的集成到 Web 环境; Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
- Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能 把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
4. shiro的总体架构
可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;
其每个 API 的含义:
- Subject: 主体,进行认证的任何东西都是Subject,可以是用户,也可以是应用程序。所有的Subject都绑定SecurityManager,与Subject的所有交互都委托给SecurityManager,可以认为Subject是一个门面,而实际的执行者是SecurityManager;
- SecurityManager:安全管理器,shiro的核心,所有与安全的相关的操作都会交给SecurityManager,负责与其他组件进行交互,可以把它看成是Spring MVC中的DispatcherServlet。
- Realm: 域,shiro从Realm中获取安全数据 (如用户、角色、权限),就是说SecurityManager想要验证用户身份需要从Realm中获取响应的用户进行比较以验证用户身份是否合法,也需要从Realm中获取相应的用户验证用户是否能进行操作;可以把Realm看成是DataSouce,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
- 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
- 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
5. shiro的详细架构
- Authenticator:认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供了ModularRealmAuthenticator实现类,基本上可以满足大多数认证需求,也可以自定义认证器。
- Authorizer: 授权器,主体通过认证器之后需要经过授权器判断主体对哪些功能有操作权限。
- SessionManager:会话管理器,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可以实现单点登录。
- CacheManager:缓存管理器,将用户权限数据存储在缓存中可以提高效率。
- SessionDAO:即会话DAO,是对sesison会话操作的一套接口,比如要将sesison存储在数据库中,可以通过jdbc将会话存到数据库中。
- Cryptography:密码管理,shiro提供了一套加密/解密的组件,方便开发,比如提供常用的散列、加/解密功能。
引用文本
shiro认证
1. 认证的概念
在shiro中用户需要提供principals (身份)和 credentials(证明)给 shiro,从而验证用户身份
2. 认证相关对象
- principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/密码/手机号。
- credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的 principals 和 credentials 组合就是用户名/密码了
3. 认证流程
流程如下:
- 首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必须通过SecurityUtils.setSecurityManager()设置;
- SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
- Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
- Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返 回/抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
4. shiro认证源码追踪
5. shiro 基于javaSE的认证程序
1.引入shiro-core依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
2.自定义Realm类,继承AuthenticatingRealm类
public class MyRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取主体的身份信息
String username = (String) token.getPrincipal();
SimpleAccount account = null;
if(username.equals("gaofei")){
//根据用户名模拟从数据库当中查询出主体,其密码是经过MD5加盐加密并散列后的值
account = new SimpleAccount("zhangsan","68609b8b64988c0f4def093eaa025e05", ByteSource.Util.bytes("abcd"),new MyRealm().getName());
}
return account;
}
}
3.编写配置文件 命名为shiro.ini
[main]
#自定义AuthenticatingRealm的是实现类
customRealm=com.baizhi.firstApplication.MyRealm
#自定义凭证匹配器
hashedCredentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
hashedCredentialsMatcher.hashAlgorithmName=MD5
hashedCredentialsMatcher.hashIterations=1024
#为Realm指定凭证匹配器
customRealm.credentialsMatcher=$hashedCredentialsMatcher
#为安全管理器设置自定义Realm
securityManager.realms=$customRealm
4.编写测试类
public class TestAuthentication{
public static void main(String[] args) {
//1.创建SecurityManager初始化工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.获得SecurityManager实例 并且绑定到SecurityUtils中
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3.获取subject实例 以及身份凭证
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken("zhangsan","123456");
try {
//4.进行身份认证
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
}finally {
boolean authentsicated = subject.isAuthenticated();
System.out.println(authentsicated);
}
}
}
shiro授权
1. 授权的概念
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
2. 授权相关对象
- Subject:主体需要访问系统中的资源
- Resource:即资源,如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例
- Permission: 权限/许可,规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限。
3. 授权流程
流程如下:
- 首先调用 Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager 接着会委托给 Authorizer;
- Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过PermissionResolver 把字符串转换成相应的 Permission 实例;
- 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
- Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配isPermitted/hasRole会返回 true,否 则返回 false 表示授权失败。
4. shiro 基于javaSE的授权程序
1.引入shiro-core依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
2.自定义Realm类,继承AuthorizingRealm类
public class MyRealm extends AuthorizingRealm {
@Override
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = null;
if(username.equals("zhangsan")){
info = new SimpleAuthorizationInfo();
//为当前主体授予角色
info.addRoles(Arrays.asList("admin","superAdmin"));
}
return info;
}
@Override
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
SimpleAccount account = null;
if(username.equals("zhangsan")){
account = new SimpleAccount("zhangsan","68609b8b64988c0f4def093eaa025e05",ByteSource.Util.bytes("abcd"),new MyRealm().getName());
}
return account;
}
}
3.编写配置文件shiro-authorization.ini
[main]
#自定义AuthenticatingRealm的是实现类
customRealm=com.baizhi.firstApplication.MyRealm
#自定义凭证匹配器
hashedCredentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
hashedCredentialsMatcher.hashAlgorithmName=MD5
hashedCredentialsMatcher.hashIterations=1024
#为Realm指定凭证匹配器
customRealm.credentialsMatcher=$hashedCredentialsMatcher
#为安全管理器设置自定义Realm
securityManager.realms=$customRealm
4.编写测试类
public class TestAuorization{
public static void main(String[] args) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-authorization.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
}finally {
System.out.println(subject.isAuthenticated());
}
//基于角色的授权
//
if(subject.isAuthenticated()){
boolean b = subject.hasAllRoles(Arrays.asList("Admin","superAdmin"));
System.out.println(b);
}
}
}
shiro中默认提供的Realm
当我们使用自定义的Realm时只需要定义一个类继承AuthorizingRealm即可,其继承了AuthenticatingRealm类和CachingRealm类,能够进行认证和授权操作,且能够实现缓存
shiro中权限标识符
权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用* 通配符。
例子:
- 用户创建权限:user:create,或user:create:*
- 用户修改实例001的权限:user:update:001
- 用户实例001的所有权限:user:*:001
shiro中相关的标签
<shiro:principal></shiro:principal> //用户的身份信息
<shiro:authenticated></shiro:authenticated> //认证成功 执行标签体的内容
<shiro:notAuthenticated></shiro:notAuthenticated> //未认证 执行标签体内容
//基于角色的权限管理
<shiro:hasRole name="super"></shiro:hasRole>
<shiro:hasAnyRoles name="admin,super"></shiro:hasAnyRoles>
//基于资源的权限管理
<shiro:hasPermission name="user:delete"></shiro:hasPermission>
shiro整合springboot案例
1.引入相关依赖
<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>
<scope>test</scope>
</dependency>
<!--jsp支持-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--shiro相关依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
2.进行相关的配置 application.yml
server:
port: 8888
servlet:
jsp:
init-parameters:
development: true
spring:
mvc:
view:
suffix: .jsp
prefix: /
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/shiro_day02
3.创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@Id
private String id;
private String username;
private String password;
private String salt;
}
5.创建DAO
public interface UserDAO extends Mapper<User> {
}
6.创建Controller
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("login")
public String login(User user){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
subject.login(token);
return "redirect:/index.jsp";
} catch (IncorrectCredentialsException e) {
System.out.println("密码输入错误");
} catch (UnknownAccountException e){
System.out.println("账户不存在");
}
return "redirect:/login.jsp";
}
@RequestMapping("exit")
public String exit(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/index.jsp";
}
}
7.配置shiro的核心过滤器
@Configuration
public class ShiroFliterConf{
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//annon 匿名资源 shiro不拦截
//authc 认证资源 shiro进行拦截
map.put("/**","authc");
map.put("/index.jsp","anon");
map.put("/user/login","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager getSecurityManger(MyRealm myRealm,CacheManager cacheManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//指定自定义的Realm
securityManager.setRealm(myRealm);
//指定缓存
securityManager.setCacheManager(cacheManager);
return securityManager;
}
@Bean
public CacheManager getCacheManager(){
CacheManager cacheManager = new EhCacheManager();
return cacheManager;
}
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myRealm;
}
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
}
8.自定义Realm类
public class MyRealm extends AuthorizingRealm {
@Resource
private UserDAO userDAO;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if("zhangsan".equals(username)){
authorizationInfo.addRoles(Arrays.asList("user","admin"));
authorizationInfo.addStringPermissions(Arrays.asList("user:select"));
}
if("lisi".equals(username)){
authorizationInfo.addRoles(Arrays.asList("user","admin","superAdmin"));
authorizationInfo.addStringPermissions(Arrays.asList("user:*"));
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
Example example = new Example(User.class);
example.createCriteria().andEqualTo("username",username);
User user = userDAO.selectOneByExample(example);
if(StringUtils.isEmpty(user)){
return null;
}else{
return new SimpleAccount(username,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
}
}
9.jsp页面
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page contentType="text/html; UTF-8" isELIgnored="false" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>首页</title>
</head>
<body>
<h2>首页</h2>
<shiro:authenticated>
您好! <shiro:principal/> <a href="${pageContext.request.contextPath}/user/exit">退出</a>
<ul>
<shiro:hasRole name="admin">
<li>轮播图管理</li>
<li>专辑管理</li>
<li>上师管理
<ul>
<shiro:hasPermission name="user:select">
<li>查询</li>
</shiro:hasPermission>
<shiro:hasPermission name="user:add">
<li>添加</li>
</shiro:hasPermission>
<shiro:hasPermission name="user:edit">
<li>修改</li>
</shiro:hasPermission>
<shiro:hasPermission name="user:del">
<li>删除</li>
</shiro:hasPermission>
</ul>
</li>
<li>用户管理</li>
</shiro:hasRole>
<shiro:hasRole name="superAdmin">
<li>管理员管理</li>
</shiro:hasRole>
</ul>
</shiro:authenticated>
<shiro:notAuthenticated>
您还未登陆,请<a href="${pageContext.request.contextPath}/login.jsp">登陆</a>
</shiro:notAuthenticated>
</body>
</html>
参考资料1