

public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
// 获取当前的 Subject. 调用 SecurityUtils.getSubject();
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
// 测试使用 Session
// 获取 Session: Subject#getSession()
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("---> Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
// 测试当前的用户是否已经被认证. 即是否已经登录.
// 调动 Subject 的 isAuthenticated()
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// rememberme
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 " + token.getPrincipal() + " was incorrect!");
return;
}
// 用户被锁定的异常 LockedAccountException
catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//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;
}
//test a typed permission (not instance-level)
// 测试用户是否具备某一个行为. 调用 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.");
}
//a (very powerful) Instance Level permission:
// 测试用户是否具备某一个行为.
if (currentUser.isPermitted("user:delete:zhangsan")) {
log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
// 执行登出. 调用 Subject 的 Logout() 方法.
System.out.println("---->" + currentUser.isAuthenticated());
currentUser.logout();
System.out.println("---->" + currentUser.isAuthenticated());
System.exit(0);
}
}整合方案 1.导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.0</version>
</dependency>2.构建数据库和Javabean



然后创建对应数据表,除去Javabean中的set类型字段。

尽管是一对多关系,但是为了显示清晰,我们使用中间表关联关系。
mapper查找用户,并返回他的角色以及权限
<select id="getUserByUsernameAndPsw" resultMap="user">
select
u.*,
r.*,
p.*
from user u
left join user_role ur
on ur.user_id=u.id
left join role r
on ur.role_id=r.rid
left join role_permi rp
on rp.role_id=r.rid
left join permission p
on rp.permi_id=p.pid
where u.username=#{username} and u.psw=#{psw}
</select>
<resultMap id="user" type="com.example.demo.web.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<collection property="roles" resultMap="role"/>
</resultMap>
<resultMap id="role" type="com.example.demo.web.entity.Role">
<id column="rid" property="id"/>
<result column="role_name" property="roleName"/>
<collection property="permissions" ofType="com.example.demo.web.entity.Permission">
<id column="pid" property="id"/>
<result column="permission_name" property="permissionName"/>
</collection>
</resultMap>
Shiro 提供了与 Web 集成的支持,其通过一个ShiroFilter 入口来拦截需要安全控制的URL,然后进行相应的控制
ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini 配置文件),然后判断URL是否需要登录/权限等工作。


匹配模式
匹配顺序
pom
<!--整合shiro缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.0</version>
</dependency>ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="java.io.tmpdir"/>
<cache name="users"
timeToLiveSeconds="300"
maxEntriesLocalHeap="1000"/>
<!--
name:缓存空间名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>ShiroConfig.java
/**
* 缓存管理器
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
/**
* 开启注解模式,aop代理
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 自定义realm
*
* @return
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
userRealm.setCacheManager(ehCacheManager());
return userRealm;
}
/**
* 安全管理器注入缓存
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
securityManager.setCacheManager(ehCacheManager());
return securityManager;
}EhcacheConfig
package com.bigdata.dangjian.web.config;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author sxuer
*/
@Configuration
public class EhcacheConfig {
/**
* 设置为共享模式
* @return
*/
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
cacheManagerFactoryBean.setShared(true);
return cacheManagerFactoryBean;
}
}给realm类的service添加懒加载
UserRealm里注入的SysUserService等service,需要延迟注入,所以都要添加@Lazy注解(如果不加需要自己延迟注入),否则会导致该service里的@Cacheable缓存注解、@Transactional事务注解等失效。
public class UserRealm extends AuthorizingRealm {
@Autowired
@Lazy
IUserService userService;
/**
* 如果权限更改了,调用清理缓存
*/
public void clearCache() {
Subject currentUser = SecurityUtils.getSubject();
super.clearCache(currentUser.getPrincipals());
}此时仅仅是缓存了权限认整,如果要对mybatis缓存,在对应方法/类上加
@CacheConfig(cacheNames = {"users"})
@Cacheable

subject.login(token)的token,传递给了reaml(继承AuthenticatingReaml的类的AuthenticationInfo方法中)

权限注解生成了代理对象,如果使用springAOP,也是通过代理实现的; 由于不允许代理的代理(会出现类型转换异常),因此,不要在AOP的地方进行shiro注解
Shiro session能够在service层获取session
setLoginUrl 设置的是控制器路径
调用login,进入AuthenticationInfo进行认证


登录过程: 1.getSubject() 1.1从ThreadLocal中获取到servletRequest等信息(Subject就是访问信息,测试环境是web,因此返回如下)

2.subject.login(token) 2.1如果session不为空,就移除RUN_AS_PRINCIPALS_SESSION_KEY(清空session) 2.2 this.securityManager.login(subject,token);拿到登陆过后的Subject 2.2.1 进行token校验 检查realm是否存在; 将token传递给自定义的Reaml中的登录认证,完成密码校验,并返回生成的info,保存缓存 2.2.3 登录成功后保存新的loggedInSubject并返回Manager 2.3 保存相关信息,创建session,返回到subject.login(token)
大致逻辑: 从Request中获取登录信息,同时清空session,进行一系列框架需求检查(如realm是否存在),进行token校验(这个是用户在Reaml中指定逻辑),验证成功后shiro保存相关缓存信息(但是浏览器返回之后再点登录,依旧需要执行Reaml中的认证逻辑,这里需要手动进行缓存)。
权限校验过程: 1.servlet doFilterInternal将Request经过过滤链 包括spring的过滤链(字符集、HTTP方法支持等) 自定义路由过滤链(如检查是否是anon类型的API) 自定义的其他filter 2.通过aop拿到所需权限,然后cblib动态代理获取到执行方法并执行。
误区:@RequiredPermissions不会触发doGetAuthorizationInfo,登录的时候会触发,通过subject判断是否拥有角色和权限的时候会触发,@RequiredRoles会触发; 登录的时候先进行登录认证,认证完毕立马做授权,授权之后将用户的权限保存到缓存中,需要权限校验的时候再去缓存中查询。 即:登录的时候就保存了用户的所有权限信息,而非是用户访问接口的时候再去查询或保存到缓存。