这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
一、Shiro概述
1.1、权限管理
1.1.1、权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制
,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证
和授权
两部分,简称认证授权
。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
1.1.2、身份认证
身份认证
,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
1.1.3、授权
授权,即访问控制
,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
2.1、什么是shiro
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
2.2、为什么要学shiro
- 既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
- shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
2.3、基本功能
- Authentication
身份认证/登录,验证用户是不是拥有相应的身份;
- Authorization
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用 户对某个资源是否具有某个权限;
- SessionManager
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信 息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
- Cryptography
加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- WebSupport
Web 支持,可以非常容易的集成到Web 环境;
- Caching
缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
- Concurrency
shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能 把权限自动传播过去;
- Testing
提供测试支持;
- Run As
允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me
记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录 了。
注意:Shiro 不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。关于设计,后面的ssm集成shiro里面去说哦
2.4、架构说明
- Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
- SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
- Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
- Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
- Realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
**注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。 **
- sessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
- SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
- CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
- Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
2.5、下载Shiro和maven的依赖
- shiro-all 是shiro的所有功能jar包
- shiro-core 是shiro的基本功能包
- shiro-web 和web集成的包
- shiro-spring shrio和spring集成的包
二、Shiro基础入门
2.1、Shiro.ini文件的说明
-
ini (InitializationFile) 初始文件.Window系统文件扩展名.
-
Shiro 使用时可以连接数据库,也可以不连接数据库. 如果不连接数据库,可以在shiro.ini中配置静态数据
2.2、Shrio.ini文件的组成部分
[main] :定义全局变量
-
内置securityManager对象.
-
操作内置对象时,在[main]里面写东西
[main]
securityManager.属性=值
xiaolin=123456
securityManager.对象属性=$xiaolin
复制代码
[users] :定义用户名和密码
[users]
# 定义用户名为zhangsan 密码为zs
zhangsan=zs
# 定义用户名lisi密码为lisi同时具有role1和role2两个角色
lisi=lisi,role1,role2
复制代码
[roles]:定义角色
[roles]
role1=权限名1,权限名2
role2=权限3,权限4
复制代码
[urls] :定义哪些内置urls生效.在web应用时使用.
[urls]
#url地址=内置filter或自定义filter
# 访问时出现/login的url必须去认证.支持authc对应的Filter
/login=authc
# 任意的url都不需要进行认证等功能.
/** = anon
# 所有的内容都必须保证用户已经登录.
/**=user
# url abc 访问时必须保证用户具有role1和role2角色.
/abc=roles[“role1,role2”]
复制代码
2.3、过滤器详解
过滤器名称 | 过滤器类 | 描述 |
---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名过滤器 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 如果继续操作,需要做对应的表单验证否则不能通过 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | 基本http验证过滤,如果不通过,跳转屋登录页面 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 登录退出过滤器 |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter | 没有session创建过滤器 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限过滤器 |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口过滤器,可以设置是否是指定端口如果不是跳转到登录页面 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | http方法过滤器,可以指定如post不能进行访问等 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色过滤器,判断当前用户是否指定角色 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | 请求需要通过ssl,如果不是跳转回登录页 |
user | org.apache.shiro.web.filter.authc.UserFilter | 如果访问一个已知用户,比如记住我功能,走这个过滤器 |
anon: 匿名处理过滤器,即不需要登录即可访问;一般用于静态资源过滤;
如: /static/**=anon
authc: 表示需要认证(登录)才能使用;
如:/**=authc
roles:角色授权过滤器,验证用户是否拥有资源角色;
如:/admin/*=roles[admin]
perms:权限授权过滤器,验证用户是否拥有资源权限;
如:/employee/input=perms[“user:update”]
logout:注销过滤器
如: /logout=logout
三、认证
3.1、认证
认证的过程即为用户的身份确认过程,所实现的功能就是我们所熟悉的登录验证,用户输入账号和密码提交到后台,后台通过访问数据库执行账号密码的正确性校验。
3.2、认证中的关键对象
3.2.1、Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为体;
3.2.2、Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性
,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
3.2.3、credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
3.3、认证流程
最终执行用户比较是在 SimpleAccountRealm
类中的doGetAuthenticationInfo
方法完成了用户名的校验。
最终密码校验是在 AuthenticatingRealm
类中的assertCredentialsMatch
方法中完成密码的校验,且是自动的,不需要我们手动完成。
3.4、代码实现
3.4.1、引入依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
<scope>provided</scope>
</dependency>
复制代码
3.4.2、编写ini文件
[users]
xiaolin=666
zs=13
复制代码
3.4.3、编写测试类
/**
* 用于测试Shiro基础
*/
@Test
public void testShiroBase(){
// 创建Shiro安全管理器,是Shiro的核心
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 加载ini配置文件,得到配置文件中配置的用户信息
IniRealm iniRealm = new IniRealm("classpath:shiro-authc.ini");
// 将realm对象注入到securityManager中
securityManager.setRealm(iniRealm);
// 将安全管理器注入到当前环境中
SecurityUtils.setSecurityManager(securityManager);
// 获取subject主体对象,无论是否登录都可以获取
Subject subject = SecurityUtils.getSubject();
// 打印一下认证状态
System.out.println("认证状态"+subject.isAuthenticated());
// 创建一个携带了账号和密码的令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaolin", "666");
// 调用subject传入令牌进行登录
subject.login(token);
System.out.println("认证状态"+subject.isAuthenticated());
}
复制代码
3.4.4、注意
如果输入的身份和凭证和 ini 文件中配置的能够匹配,那么登录成功,登录状态为true,反之登录状态为false。
登录失败一般存在几种情况(抛出的异常):
- 账号错误:
org.apache.shiro.authc.UnknownAccountException
- 密码错误:
org.apache.shiro.authc.IncorrectCredentialsException
3.4.5、Shiro认证源码分析
1、调用subject.login方法进行登录,其会自动委托给securityManager.login方法进行登录;
2、securityManager通过Authenticator(认证器)进行认证;
3、Authenticator的实现类ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带,相当于数据源);
4、IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。
3.5、自定义Realm
自定义 Realm 在实际开发中使用非常多,应该我们需要使用的账户信息通常来自程序或者数据库中, 而不是前面使用到的 ini 文件的配置。
在 AuthenticatingRealm 中调用doGetAuthenticationInfo方法来获取,如果返回的 info不等于空,说明账号存在,才会进行密码校验,如果不存在则直接抛出UnknownAccountException异常。
所以,如果我们要自定义 Realm,应该覆写 doGetAuthenticationInfo()方法,然后在该方法中实现账号的校验,并返回 AuthenticationInfo 对象给上层调用者 AuthenticatingRealm 做进一步的校验。
3.5.1、Shiro提供的Realm
3.5.2、SimpleAccountRealm
我们进行Debug的时候,可以看到认证的源码中使用的是SimpleAccountRealm。
3.5.3、SimpleAccountRealm的部分源码
// 认证
public class SimpleAccountRealm extends AuthorizingRealm {
//.......省略
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
return this.users.get(username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
复制代码
3.6、SSM整合Shiro认证
3.6.1、添加依赖
<properties>
<shiro.version>1.5.2</shiro.version>
</properties>
<!--shiro 核心-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro 的 Web 模块-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro 和 Spring 集成-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro 底层使用的 ehcache 缓存-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro 依赖的日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--Freemarker 的 shiro 标签库-->
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>1.0.1</version>
</dependency>
复制代码
3.6.2、配置代理过滤器
在访问的时候,需要做一系列的预处理操作,Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用。我们需要在web.xml进行配置过滤器。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
这里使用了一个代理过滤器DelegatingFilterProxy,因为真正的shiroFilter需要注入很多复杂的对象,而web.xml中只能配置字符串或数字的参数,是不能满足的,因此我们会把shiroFilter交给 Spring 进行管理,通过spring的xml文件来配置。 使用DelegatingFilterProxy代理过滤器后,但浏览器发送请求过来,被代理过滤器拦截到后,代理过滤器会自动从 spring 容器中找filter-name所配置相同名称的bean,来实现真正的业务。
3.6.3、创建shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--自定义Realm-->
<bean id="myRealm" class="cn.linstudy.shiro.CarBusinessRealm"/>
<!--创建安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--引用指定的安全管理器-->
<property name="securityManager" ref="securityManager"/>
<!--shiro默认的登录地址是/login.jsp 现在要指定我们自己的登录页面地址-->
<property name="loginUrl" value="/login.html"/>
<!--路径对应的规则-->
<property name="filterChainDefinitions">
<!--配置请求拦截的方式,是可以匿名还是-->
<value>
/empLogin=anon
/static/**=anon
/upload/**=anon
/getImage=anon
/getEmailCode=anon
/checkEmail=anon
/checkUsername=anon
/error=anon
/fronted/**=anon
/**=authc
</value>
</property>
</bean>
</beans>
复制代码
3.6.4、引入shrio.xml
在mvc.xml 中引入shiro.xml
<import resource="classpath:shiro.xml"/>
复制代码
3.6.5、配置安全管理器
我们需要在shiro.xml中配置安全管理器。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean>
复制代码
3.6.6、配置自定义Realm
public class CarBusinessRealm extends AuthorizingRealm {
@Autowired
private EmployeeService employeeService;
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 通过token获取用户名(用户登录的时候)
String username = (String) token.getPrincipal();
// 判断是否存在在数据库
Employee employee = employeeService.selectByUsername(username);
if (employee != null) {
// 说明此时用户名是对的,返回一个身份对象
return new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
}
return null;
}
}
复制代码
3.6.7、配置自定义Realm
<bean id="myRealm" class="cn.linstudy.shiro.CarBusinessRealm"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
复制代码
3.6.8、修改登录方法
try {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
SecurityUtils.getSubject().login(token);
Employee employee = employeeMapper.selectByUsername(username);
return employee;
} catch (UnknownAccountException e) {
throw new CarBussinessException("用户名错误");
} catch (IncorrectCredentialsException e) {
throw new CarBussinessException("密码错误");
}
复制代码
3.7、登出
shiro中内置了登出方法,我们只需在shiro.xml中的路径规则加入 /logout=logout 即可交给shiro来处理。