Shiro权限学习

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图:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值