Shiro是Apache的一个安全框架. 可以完成 认证 授权 加密 会话管理 与web集成 缓存等.
(1)基本功能点:
Authentication: 身份认证/登录,验证用户是不是拥有相应的身份;
Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用
户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户
对某个资源是否具有某个权限;
Session Manager: 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中; 会话可以是普通 JavaSE 环境, 也可以是 Web 环境的;
Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support: Web 支持,可以非常容易的集成到Web 环境;
Caching: 缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可 以提高效率;
Concurrency: Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As: 允许一个用户假装为另一个用户(如果他们允许) 的身份进行访问;
Remember Me: 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
(2)shiro架构
• Subject: 应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。 Subject 代表了当前“用户” , 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等; 与 Subject 的所有交 互都会委托给 SecurityManager;Subject 其实是一个门面, SecurityManager 才是实际的执行者;
• SecurityManager: 安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色
• Realm: Shiro 从 Realm 获取安全数据(如用户、角色、权限), 就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource.
(3)HelloWorld程序
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log =LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
//认证工厂
Factory<SecurityManager> factory = new
IniSecurityManagerFactory("classpath:shiro.ini");
//认证管理器
SecurityManager securityManager = factory.getInstance();
//
SecurityUtils.setSecurityManager(securityManager);
//获取Subject,用于交互
Subject currentUser = SecurityUtils.getSubject();
//操作session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
// 测试当前的用户是否已经被认证. 即是否已经登录.
// 调动 Subject 的 isAuthenticated()
if (!currentUser.isAuthenticated()) {
//Token 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken("q", "vespa");
token.setRememberMe(true);
try {
// 执行登录.
currentUser.login(token);
}
// 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常.
catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
return;
}
// 若账户存在, 但密码不匹配, 则shiro会抛出 IncorrectCredentialsException 异常。
catch (IncorrectCredentialsException ice) {
log.info("Password for account was incorrect!");
return;
}
// 用户被锁定的异常 LockedAccountException
catch (LockedAccountException lae) {
log.info("The account for username is locked. " +
"Please contact your administrator to unlock it.");
}
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
// 测试是否有某一个角色. 调用 Subject 的 hasRole 方法.
if (currentUser.hasRole("schwartz")) {
log.info("----> May the Schwartz be with you!");
} else {
log.info("----> Hello, mere mortal.");
return;
}
// 测试用户是否具备某一个行为. 调用 Subject 的 isPermitted() 方法。
if (currentUser.isPermitted("lightsaber:weild")) {
log.info("----> You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 测试用户是否具备某一个行为.
if (currentUser.isPermitted("user:delete:zhangsan")) {
//具备权限
} else {
//不具备
}
// 执行登出. 调用 Subject 的 Logout() 方法.
System.out.println("---->" + currentUser.isAuthenticated());
currentUser.logout(); //执行登出操作
System.out.println("---->" + currentUser.isAuthenticated());
System.exit(0);
}
}
(4) web环境下整合Shiro
applicationContext.xml配置 其中jdbcrealm需要自定义并实现AuthorizingRealm,以下配置是单realm的配置
<?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-3.0.xsd">
<!-- securityManager核心管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="jdbcRealm"/>
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 多Realm的配置 -->
<bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密方式 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- 定义加密次数 -->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
<bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA1"></property>
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
<!-- 让Spring IOC管理bean的生命周期 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启注解,需要依赖lifecycleBeanPostProcessor -->
<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>
<!-- shiroFilter与web.xml配置的名称一样,否则会找不到,也可以在web.xml中配置tagname -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/s/login"/>
<property name="successUrl" value="/s/index"/>
<property name="unauthorizedUrl" value="/s/unauthorized"/>
<property name="filterChainDefinitions">
<value>
/favicon.ico = anon #匿名访问
/logo.png = anon
/shiro.css = anon
/s/login = anon
# allow WebStart to pull the jars for the swing app:
/*.jar = anon
# everything else requires authentication:
/** = authc #必须授权才能访问
</value>
</property>
</bean>
</beans>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<!-- web容器启动加载Spring的IOC容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- SpringMVC的核心控制器 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--
1. 配置 Shiro 的 shiroFilter.
2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容
器中查找和
<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter
bean 的 id.
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<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>
</web-app>
实现AuthorizingRealm的接口
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
ByteSource credentialsSalt = ByteSource.Util.bytes(username); //密码加盐操作
SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
// principal 用户名或者用户的实体 credentials 密码 realmName 不重要随便写
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); //credentialsSalt 盐值
}
(5)认证策略
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!-- 认证策略 三种
• FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第
一个 Realm 身份验证成功的认证信息,其他的忽略;
• AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可, 和
FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;
• AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有
Realm身份验证成功的认证信息,如果有一个失败就失败了。
-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
*******在authoticator中可以配置realms属性指向多个realm, 也可以在securityManager中配置realms属性,但是推荐后者,在授权时可以使用.
(6) 授权
Shiro三种授权的方式: 1)编程式 2)注解式 3)jsp标签
/admin.jsp = roles[admin] # 需要admin相关的角色,否则将进入到未授权的页面
authotication (只是认证)--------> authorizingRealm(认证和授权)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//1. 从当前的subject中获取用户的信息
Object principal = principals.getPrimaryPrincipal();
//2. 获取用户的角色信息
// 在shirofilter时的配置 /admin.jsp = roles[admin] /user.jsp = roles[user]
Set<String> roles = new HashSet<>();
roles.add("user");
if("admin".equals(principal)){
roles.add("admin"); //对于admin的用户同时拥有两个角色的信息
}
//3. 为当前的认证信息封装角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4. 返回信息
return info;
}
(7)shiro的标签
<shiro:principal></shiro:principal> 当前认证过的用户信息
<shiro:hasrole name='admin'>********</shiro:hasrole>
(8)shiro的权限注解
• @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证; 即 Subject. isAuthenticated() 返回 true
• @RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。
• @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
• @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):表示当前 Subject 需要角色 admin 和user
• @RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示当前 Subject 需要权限 user:a 或user:b。
开发时配置在Controller中
(9)认证和授权的另外一种配置方式,linkedHashmap 封装连接地址
<!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
<bean id="filterChainDefinitionMap"
factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean>
<bean id="filterChainDefinitionMapBuilder"
class="com.atguigu.shiro.factory.FilterChainDefinitionMapBuilder">
</bean>