一 shiro 是什么
shiro 是一个功能强大和易于使用的Java安全框架,为开发人员提供一个直观而全面的解决方案的认证,授权,加密,会话管理。
二 shiro 能干什么
先上图:
所有功能
shiro 四个主要的功能
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,判断某个已经认证过的用户是否拥有某些权限访问某些资源,一般授权会有角色授权和权限授权;
- SessionManager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的,web 环境中作用是和 HttpSession 是一样的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
shiro 的其它几个特点
- Web Support:Web支持,可以非常容易的集成到Web环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
- Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
三 shiro 架构
先上图
架构
从图中我们可以看到不管是任何请求都会经过 SecurityManager 拦截并进行相应的处理,shiro 几乎所有的功能都是由 SecurityManager 来管理。
其中:
- Subject:主体,相当与是请求过来的"用户"
- SecurityManager: 是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行拦截并控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理
- Authenticator:认证器,负责主体认证的,即确定用户是否登录成功,我们可以使用 Shiro 提供的方法来认证,有可以自定义去实现,自己判断什么时候算是用户登录成功
- Authrizer:授权器,即权限授权,给 Subject 分配权限,以此很好的控制用户可访问的资源
- Realm:一般我们都需要去实现自己的 Realm ,可以有1个或多个 Realm,即当我们进行登录认证时所获取的安全数据来源(帐号/密码)
- SessionManager:为了可以在不同的环境下使用 session 功能,shiro 实现了自己的 sessionManager ,可以用在非 web 环境下和分布式环境下使用
- SessionDAO:对 session 的 CURD 操作
- CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;
- Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
四 shiro 的主要功能 - 身份认证
1 Subject 认证
身份认证就是在应用中谁能证明他就是他本人,一般会使用用户名和密码作为认证信息。
2 Subject 认证主体
Subject 认证主体包含两个信息:
- Principals:身份,即用户名
- Credentials:凭证,即密码
** 3 认证流程**
认证流程- 用户发送请求进行 Subject 认证(调用 subject.login(token))
- SecurityManager 会去 Authenticator(认证器)中查找相应的 Realms(可能不止一个)源
- Realms 可以根据不同类型的 Realm 中去查找用户信息,并进行判断是否认证成功
4 快速搭建 helloWorld
- 导包
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
- 创建 Realm /resources/shiro.ini
[users]
acey=123456
jack=111
- 进行身份验证
public class HelloWorld {
public static void main(String[] args) {
// 加载配置文件,初始化 SecurityManager 工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory
("classpath:shiro.shiro.ini");
// 获取 SecurityManager 实例
SecurityManager securityManager = factory.getInstance();
// 把 SecurityManager 绑定到 SecurityUtils 中
SecurityUtils.setSecurityManager(securityManager);
// 得到当前执行的用户
Subject currentUser = SecurityUtils.getSubject();
// 创建 token 令牌,用户名/密码
UsernamePasswordToken token = new UsernamePasswordToken("acey", "123456");
try {
// 身份验证
currentUser.login(token);
System.out.println("登录成功");
} catch (Exception e) {
e.printStackTrace();
System.out.println("登录失败");
}
}
}
四 shiro 的主要功能 - 授权
权限授权就是访问控制,在应用中控制谁能访问哪些资源
1 权限认证中的几个元素
- 权限:即操作某个资源的权限,这些资源可以是某个链接,也可以是某个图片,也可以是对某个模块的数据的 CURL
- 角色:即权限的集合,一个角色可以有多个权限
- 用户:代表访问的用户,即 Subject
2 授权的流程
授权流程- 当用户访问应用中的某个资源时,会被 SecurityManager 拦截.
- SecurityManager 会去调用 Authorizer(授权器)
- Authorizer 会根据 Subject 的身份去相应的 Realm 中去查找该 Subject 是否有权限去访问该资源
3 授权实现
- 导包
- 配置 permission(权限) resources/shiro_permission.ini
[main]
authc.loginUrl=/login //表示用户登录失败跳转到 /login
roles.unauthorrizedUrl=/unauthorrized.jsp //表示用户没有对应的访问角色跳转到/unauthorrized.jsp
perms.unauthorrizedUrl=/unauthorrized.jsp //表示用户没有对应的访问权限跳转到/unauthorrized.jsp
[users]
acey=123456,role1,role2
jack=123,role1
[roles]
role1=user:select // role1 角色有访问 user:select 的权限
role2=user:add,/delete //role2 角色有访问 user:add 和 /delete 的权限
[urls]
/login=anon //表示任何用户都可以访问 /login
/index=authc //表示只有身份认证通过的用户才可以访问 /index
/index=roles[role1,role2...] //表示只有用户含有 role1 role2 ... 角色才可以访问 /index
/index=perms["user:create","/update"] //表示只有用户含有 "user:create"
和"/update"权限才可以访问 /index
/index?=authc //`?`通配符,表示一个字符,如/index1 /indexa /index- (不能匹配/index) ,
将符合这种规则的请求进行`authc`拦截
/index*=authc `*`通配符,表示零个或一个或多个字符,如/index1213asd /index /index2 ,
将符合这种规则的请求进行`authc`拦截
/index/**=authc `**`表示匹配零个或一个或多个路径,如/index/create /index/create/update/... ,
将符合这种规则的请求进行`authc`拦截
/index*/**authc 可以匹配 /index12/create/update/...
3)配置 roles (角色) resources/shiro_role.ini
[users]
acey=123456,role1,role2 //表示有一个用户,用户名是acey,密码为123456,有role1和role2角色
jack=123,role1
- 验证用户角色是否足够
public class RoleTest {
// 使用 checkRole 来检验角色时,若权限不足会返回 false
@Test
public void testHasRole() {
Subject currentUser= ShiroUtil.login("classpath:shiro_role.ini", "acey", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "jack", "123");
System.out.println(currentUser.hasRole("role1")?"has role1":"has not role1");
currentUser.logout();
}
// 使用 checkRole 来检验角色时,若权限不足会抛出异常
@Test
public void testCheckRole() {
Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "acey", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "jack", "123");
currentUser.checkRole("role1");
currentUser.logout();
}
}
- 验证用户权限是否足够
public class PermissionTest {
// 使用 checkPermission 来检验权限时,若权限不足会返回 false
@Test
public void testIsPermitted() {
Subject currentUser= ShiroUtil.login("classpath:shiro_permission.ini", "acey", "123456");
System.out.println(currentUser.isPermitted("user:select")?"has user:select":"hsa not user:select");
currentUser.logout();
}
// 使用 checkPermission 来检验权限时,若权限不足会抛出异常
@Test
public void testCheckPermitted() {
Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "acey", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "jack", "123");
currentUser.checkPermission("user:select");
currentUser.logout();
}
}
五 ssm 和 shiro 整合
- 导入依赖
2)配置 web.xml(shiro过滤器)
<!-- shiro过滤器定义 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3)编写自己的 Realm(一般权限都是从数据库中查找,所以需要自定义)
public class MyRealm extends AuthorizingRealm{
@Resource
private UserService userService;
/**
* 为当前登录的用户授予角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取用户名
String userName=(String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
//进行授权角色
authorizationInfo.setRoles(userService.getRoles(userName));
//进行授权权限
authorizationInfo.setStringPermissions(userService.getPermissions(userName));
return authorizationInfo;
}
/**
*验证当前登录的用户
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String)token.getPrincipal();
//根据用户名查找用户信息
User user=userService.getByUserName(userName);
if(user!=null){
AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),getName());
return authcInfo;
}else{
return null;
}
}
}
- spring 和 shiro 配置整合
...
<!-- 自定义Realm -->
<bean id="myRealm" class="com.acey.realm.MyRealm"/>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!-- Shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/index.jsp"/>
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthor.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/login=anon
/admin*=authc
/student=roles[teacher]
/teacher=perms["user:create"]
</value>
</property>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
...
一般角色和权限都存在数据库中,所以我们还可以自定义一个 filter 去自己验证每一个请求的 Subject 是否有权限去访问,这样我们就可以减少对过滤链的维护.比如
<!-- Shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/index.jsp"/>
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthor.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/login=anon
/admin*=authc
/student=roles[teacher]
/teacher=perms["user:create"]
</value>
</property>
</bean>
可以改成
<!-- Shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/index.jsp"/>
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthor.jsp"/>
<property name="ownFilter" class="ownFilter.class">
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/login=anon
/**=ownFilter
</value>
</property>
</bean>
待续! 欢迎大家拍砖
源码地址:ShirDemos
网友评论
shiro_4 是做了一个 Spring 的集成,首先不要使用 SSM 这种不好的、无意义的缩写,shiro 是可以支持配置的身份库的,那用不用 MyBaits 就没什么区别了;UserService wrap 了一层 DAO,无意义,反倒是可以讲 challenge 的过程放到这个 service 中,让 controller 更清晰。controller 中不需要直接输出 system.out ,可以使用 java.util.log,抓异常的方式太差劲,另外,真的需要 serlevt 的 request 吗?我看不需要的,既然已经有框架,为何需要底层的 object 呢?
UserMapper.xml 中 SQL 语句需要优化,特别是 getPermissions,另外 role,permission 都需要建模为 entity。
这个例子中完全不需要加入 MVC ,反倒会误导别人。你要做的是写一个简单的 service 就可以说明这个东西是可以和 spring 一起工作就可以了。我觉得只需要第四个 demo 都够了,其他的都无意义。建议使用 spring-boot,让你需要些更少的代码(一个 controller,一个 service 就足够了)。
最后一个 demo 是最需要 unit test 的,但是木有……
小细节要注意,indent、package name、method signature 、comments 等等……