Shiro
文章目录
shiroDemo
Shiro和Spring Security比较
- Shiro:
- 简单、灵活
- 可以脱离Spring
- 粒度较粗
- Spring Security:
- 复杂、笨重
- 不可以脱离Spring
- 粒度更细
Shiro QuickStart
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
//认证
currentUser.login(token); //通过抛出的异常来判断用户登录结果
//授权
currentUser.hasRole
currentUser.isPermitted
currentUser.isAuthenticated()
current.logout();
ShiroWeb
基础框架搭建:shiro配置三板斧
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author GLoRy
* @date 2021/4/29 23:36
*/
@Configuration
public class ShiroConfig {
/**
* 1. Realm 代表系统资源
*
* @return MyRealm
*/
@Bean
public Realm myRealm() {
return new MyRealm();
}
/**
* 2. SecurityManager 安全管理
* 流程控制
*
* @return securityManager
*/
@Bean
public DefaultWebSecurityManager mySecurityManager(Realm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
/**
* 3. ShiroFilterFactoryBean 请求过滤器
*
* @return factoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManger) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(mySecurityManger);
return factoryBean;
}
}
认证流程:
1、获取当前用户
2、根据当前用户获取数据库中的用户信息
3、当前用户和数据库中的用户进行密码匹配
shiro在后面会帮我们进行密码比较
实现登录验证功能
1、创建自己的Realm对象,继承AuthorizingRealm
实现父类的doGetAuthenticationInfo认证方法
2、配置路径过滤器
//配置路径过滤器
Map<String, String> filterMap = new ConcurrentHashMap<>();
//key是ant路径,支持**(代表多级路径),*(代表单级路径),?(代表一个字符)
filterMap.put("","")
factoryBean.setFilterChainDefinitionMap(filterMap);
目前已经实现的功能:
1、已经可以正常判断用户名和密码
2、两个资源路径需要登录才可以访问,否则跳转到login.jsp
修复登录认证错误的访问情况
设置登录页、登录成功页、未经授权页
登出:有两种方式
- 方式一:
@RequestMapping("/logout")
public void logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
- 方式二:使用Shiro提供的logout过滤器
//配置登出过滤器
filterMap.put("/common/logout","logout");
实现授权功能
目标:
1、控制主页上按钮的访问权限
currentUser.getPricipal() 来自于 MyRealm中 doGetAuthenticationInfo() 认证方法返回的 simpleAuthenticationInfo 对象的第一个属性。
2、控制后台资源路径的访问权限
- 方法1:硬编码的方式,自行判断权限
//硬编码方式
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isPermitted("mobile")) {
return "mobile";
} else {
return "error";
}
-
方法2:使用shiro提供的perms过滤器,集中配置权限信息
//表示两个资源路径都需要登录才能访问 filterMap.put("/mobile/**", "authc,perms[mobile]"); filterMap.put("/salary/**", "authc,perms[salary]"); factoryBean.setUnauthorizedUrl("/common/unauthorized");
- 错误补充机制:没有权限就会进入ShiroFilterFactoryBean中配置的UnauthorizedUrl
@RequestMapping("/unauthorized") public Object unauthorized() { return "未经授权,无法访问"; }
-
方法3:使用shiro提供的注解实现方法级别的权限控制
- @RequiresAuthentication 需要用户完成登录认证
- @RequiresGuest 未登录用户可以访问,登录用户就不可以进行访问
- @RequiresPermissions 需要有对应资源权限
- @RequiresRoles 需要对应的角色
- @RequiresUser 需要完成用户登录并且实现了 remember me 功能
错误补充机制:没有权限就会抛出异常
@RestControllerAdvice public class MyExceptionHandler { @ExceptionHandler(UnauthorizedException.class) public Object shiroHandler() { return "请先获得对应的资源,再进行访问"; } }
密码加密
shiro会获得一个 CredentialsMatcher 对象来对密码进行比对
想要使用MD5方式进行加密:
- Md5CredentialsMatcher已经过期,要使用HashedCredentialsMatcher并设定算法名
public class HashedCredentialsMatcher extends SimpleCredentialsMatcher {
/** hash算法 */
private String hashAlgorithm;
/** hash迭代 */
private int hashIterations;
/** 加Salt进行hash散列 (已过时的方法) */
private boolean hashSalted;
//存储的凭证以十六进制编码
private boolean storedCredentialsHexEncoded;
}
-
加盐加密:
需要在认证返回的认证信息simpleAuthenticationInfo中,指定需要加的盐salt。这样算出来的密文,才可以和数据库中的密文进行比对。
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置哈希算法名称 比如 MD5
matcher.setHashAlgorithmName("MD5");
//设置哈希迭代次数
matcher.setHashIterations(5);
//设置十六进制编码的存储凭据
matcher.setStoredCredentialsHexEncoded(true);
//设置凭据匹配器
myRealm.setCredentialsMatcher(matcher);
---------------------------------------------------
//定义加盐算法的salt
ByteSource salt = ByteSource.Util.bytes("salt");
//返回authenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo
(userBean, userBean.getUserPass(), salt, "myRealm");
---------------------------------------------------
//密码转换计算
public static String encoder(String password) {
SimpleHash simpleHash = new SimpleHash
("MD5", ByteSource.Util.bytes(password), ByteSource.Util.bytes("salt"), 5);
return simpleHash.toString();
}
多Realm认证
要实现用户名和手机号都可以登录的功能
org.apache.shiro.authc.pam.ModularRealmAuthenticator(模块化Realm身份验证器)
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(),
authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
可以定义多个Realm,完成不同的功能
我们通过增加一个MobileRealm,就实现了按照手机号也可以登录的功能
多Realm的认证策略:
- AuthenticationStrategy接口,有三个实现类
- AllSuccessfulStrategy:需要所有Realm认证成功,才能最终认证成功。
- AtLeastOneSuccessfulStrategy:至少有一个Realm认证成功,才能最终认证成功。
- FirstSuccessfulStrategy:第一个Realm认证成功后即返回认证成功,不再进行后面的Realm认证。
//设置Realms集合,如myRealm和mobileRealm,实现用户名和手机号都可以进行认证
securityManager.setRealms(Arrays.asList(myRealm, mobileRealm));
//模块化Realm身份验证器
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
//设置身份验证策略
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
//设置Realms,才能进行验证
authenticator.setRealms(Arrays.asList(myRealm, mobileRealm));
//设置身份验证器
securityManager.setAuthenticator(authenticator);
Remember Me 功能
”记住我“的功能:
token.setRememberMe(true)
”记住我“功能对应了默认的user过滤器
如何设置“记住我”的时长,以及记到哪里
下面这两个功能Cookie和Session,是用于要在分布式部署中实现状态共享来来进行改造。
设置cookie
//设置cookie记住我
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
Cookie cookie = new SimpleCookie("rememberMe");
cookie.setHttpOnly(true);
cookie.setMaxAge(5);
rememberMeManager.setCookie(cookie);
securityManager.setRememberMeManager(rememberMeManager);
token.setRememberMe(true);
如果想将cookie存到Redis中可以写个接口去继承 AbstractRememberMeManager,RememberMeManager 接口,重写或实现一些抽象方法。
会话管理
//实现会话管理
securityManager.setSessionManager(sessionManager);
DefaultWebSecurityManager
ServletContainerSessionManager
//在集成Redis中可以重写类似这样的方法
public Session getSession(SessionKey key) throws SessionException {
if (!WebUtils.isHttp(key)) {
String msg = "SessionKey must be an HTTP compatible implementation.";
throw new IllegalArgumentException(msg);
} else {
HttpServletRequest request = WebUtils.getHttpRequest(key);
Session session = null;
HttpSession httpSession = request.getSession(false);
if (httpSession != null) {
session = this.createSession(httpSession, request.getRemoteHost());
}
return session;
}
}
认证缓存
//设置缓存管理器
MemoryConstrainedCacheManager cacheManager = new MemoryConstrainedCacheManager();
securityManager.setCacheManager(cacheManager);
如果想在分布式环境中使用Redis,因为shiro并未集成,可以自己重写RedisCacheManager 继承 AbstractCacheManager,写个接口实现Cache接口的方法。
“Remember Me”,会话管理,以及认证缓存,都可以通过扩展对应的manager接口的方式实现自己的灵活扩展,比如将信息共享到Redis。
Realm类型
UML图: