Shiro 是一款简单易用、功能强大的安全框架,帮助我们安全高效的构建企业级应用。
Shiro 能做什么
- 认证:用户登录的认证
- 权限:基于角色和权限的访问(url权限),以及颗粒化权限控制(按钮权限)
- 加密技术:Shiro的crypto包中包含了一系列的易于理解和使用发加密、哈希(aka摘要)辅助类
- session管理:可在web容器以及EJB容器中使用session,可扩展(例如我们可以通过重写sessionDao将session存储到数据库中)
- RememberMe:基于cookie的记住我的服务
Shiro 常用组件介绍
image.png- Subject:Subject其实代表的就是当前正在操作的用户,只不过因为“User”一般指代人,但是一个“Subject”可以是人,也可以是任何的第三方系统,服务账号等任何其他正在和当前系统交互的第三方软件系统。
所有的Subject实例都被绑定到一个SecurityManager,如果你和一个Subject交互,所有的交互动作都会别转换成Subject与SecurityManager的交互。 - SecurityManager: Shiro的核心,他主要用于协调Shiro内部各种安全组件,不过我们一般不用太关心SecurityManager,对于应用程序开发者来说,主要还是使用Subject的API来处理各种安全验证逻辑。
- Reaim:这是用于连接Shiro和客户系统的用户数据的桥梁。一旦Shiro真正需要访问各种安全相关的数据(比如使用用户账号来做用户身份验证以及权限验证)时,他总是通过调用系统配置的各种Realm来读取数据。
- 关于Shiro的其余核心组件参Shiro 官网或Shiro的架构等文章。
Shiro 是如何工作的
简单来说,在Spring项目中
- Shiro会将他的所有组件注册到SecurityManager中。
- 再通过SecurityManager注册到ShiroFilterFactoryBean(这个类实现了Spring的BeanPostProcessor会预先加载)中。
- 最后以filter的形式注册到Spring容器(实现了Spring的FactoryBean,构造一个filter注册到Spring容器中),实现用户权限的管理。
Shiro 如何集成
- shiro所需依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 基于thymeleaf的shiro扩展 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- ShiroConfig
@Configuration
public class ShiroConfig {
private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, String> chainDefinition = new LinkedHashMap<>();
// 静态资源与登录请求不拦截
chainDefinition.put("/js/**", "anon");
chainDefinition.put("/css/**", "anon");
chainDefinition.put("/img/**", "anon");
chainDefinition.put("/layui/**", "anon");
chainDefinition.put("/login", "anon");
chainDefinition.put("/login.html", "anon");
// 用户为授权通过认证 && 包含'admin'角色
chainDefinition.put("/admin/**", "authc, roles[super_admin]");
// 用户为授权通过认证或者RememberMe && 包含'document:read'权限
chainDefinition.put("/docs/**", "user, perms[document:read]");
// 用户访问所有请求 授权通过 || RememberMe
chainDefinition.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(chainDefinition);
// 当 用户身份失效时重定向到 loginUrl
shiroFilter.setLoginUrl("/login.html");
// 用户登录后默认重定向请求
shiroFilter.setSuccessUrl("/index.html");
return shiroFilter;
}
@Bean
public Realm realm() {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(credentialsMatcher());
realm.setCacheManager(ehCacheManager());
return realm;
}
@Bean
public CacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
@Bean
public CredentialsMatcher credentialsMatcher() {
AuthCredentialsMatcher credentialsMatcher = new AuthCredentialsMatcher(ehCacheManager());
credentialsMatcher.setHashAlgorithmName(AuthCredentialsMatcher.HASH_ALGORITHM_NAME);
credentialsMatcher.setHashIterations(AuthCredentialsMatcher.HASH_ITERATIONS);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean
public DefaultWebSecurityManager securityManager() {
log.debug("--------------shiro已经加载----------------");
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setCacheManager(ehCacheManager());
manager.setRealm(realm());
manager.setRememberMeManager(rememberMeManager());
return manager;
}
@Bean
public RememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* Shiro生命周期处理器:
* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* thymeleaf的shiro扩展
*
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
以上基本是Spring项目集成Shiro的通用配置,下面针对上述的几个Bean聊一聊
1. ShiroFilterFactoryBean:用于定义请求的拦截规则,Shiro为我们默认提供了一些项,常用如下
- anon : 请求不拦截
- authc:要求用户必须认证通过
- user:要求用户为记住我状态
- roles[xxx]:要求用户必须满足xxx角色
-
perms[xxx]:要求用户必须满足xxx权限
其实上述每一个都对应了一个Shiro过滤器
image.png
- Realm:上面提到过Realm是用于连接Shiro和客户系统的用户数据的桥梁,我们通过实现AuthorizingRealm来提供用户认证和授权两个API
public class ShiroRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
@Autowired
@Lazy // 这里lazy 是有必要的, shiro组件会预先加载,导致依赖的bean 没有生成代理对象(AOP失效)
private UserService userService;
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
if (log.isDebugEnabled()) {
log.debug(String.format("user:%s executing doGetAuthenticationInfo", username));
}
User user = userService.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException();
}
if (Constant.IS_LOCK.equals(user.getIsLock())) {
throw new LockedAccountException();
}
// ShiroUser 作为实际的 principal
ShiroUser shiroUser = new ShiroUser();
BeanUtils.copyProperties(user, shiroUser);
// SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
// principal 会被封装到 subject 中
// shiro 默认会把我们的 credentials (也就是password) 和 token 中的作对比,所以我们可以不用做密码校验
ByteSource salt = ByteSource.Util.bytes(user.getUsername());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(shiroUser, user.getPassword(), salt, getName());
if (log.isDebugEnabled()) {
log.debug(String.format("user:%s executed doGetAuthenticationInfo", username));
}
return info;
}
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
if (log.isDebugEnabled()) {
log.debug(String.format("user:%s executing doGetAuthorizationInfo", shiroUser.getUsername()));
}
AuthorizationDTO authorizationDTO = userService.getRolesAndPermissions(shiroUser.getId());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(authorizationDTO.getRoleCodeSet());
info.addStringPermissions(authorizationDTO.getPermissionCodeSet());
if (log.isDebugEnabled()) {
log.debug(String.format("user:%s executed doGetAuthorizationInfo", shiroUser.getUsername()));
}
return info;
}
}
- doGetAuthenticationInfo:认证方法,在执行subject.login(token);后,Shiro认证器会读取Realm中的该方法获取AuthenticationInfo对象(认证信息),包含principal(我们存储在shiro subject中的对象),credentials(密码)。
- doGetAuthorizationInfo:授权方法,在需要校验用户访问权限的时候,Shiro授权器会读取Realm中的该方法获取AuthorizationInfo对象(授权信息)读取DB后,可以通过addRoles(roleCollection)和addStringPermissions(permCollection)设置当前用户的角色和权限。Shiro在拿到这个权限信息后,会去找缓存管理器。以当前subject的principal作为key缓存起来。
3. CredentialsMatcher:密码匹配器,用于匹配doGetAuthenticationInfo方法返回的credentials和subject.login(token),时的token中的password是否一致。常用的实现有SimpleCredentialsMatcher(默认是该实现)、HashedCredentialsMatcher(该实现可以进行加密匹配)
4. DefaultWebSecurityManger:如上述,用于协调Shiro内部各种安全组件,我们需要将我们扩展的bean注册到SecurityManager中
5. RememberMeManager:开启该组件后使用记住服务,token中rememberMe为true时,登录成功之后会创建RememberMe cookie。
关于thymeleaf-extras-shiro
Shiro默认支持在jsp中使用shiro标签。但是想在thymeleaf中使用Shiro标签呢?
使用thymeleaf-extras-shiro 完美解决 thymeleaf颗粒化权限控制
你好, <span th:text="${principal}"></span><br>
<p shiro:hasRole="super_admin">当前角色超级管理员</p>
<button shiro:hasPermission="'sys:user:add'">添加</button>
<button shiro:hasPermission="'sys:user:update'">编辑</button>
<button shiro:hasPermission="'sys:user:lock'">冻结</button>
<div shiro:hasAllPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
<span>满足所有权限时显示</span>
</div>
<div shiro:hasAnyPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
<span>满足一个权限即可显示</span>
</div>
网友评论