admin管理员组文章数量:1794759
shiro安全框架
企业中使用的主流的权限框架主要有 Apache shiro或者Spring Security,两者有哪些区别呢?
-
Apache Shiro比Spring Security , 前者使用更简单
-
Shiro 功能强大、 简单、灵活, 不跟任何的框架或者容器绑定,可以独立运行
-
Spring Security 对Spring 体系支持比较好,脱离Spring体系则很难开发
-
SpringSecutiry 支持Oauth鉴权 Spring Security OAuth,Shiro需要自己实现
不过企业中使用shiro的会比较多,因为上手会比较容易
一、Shiro的简单介绍
Apache Shiro官网 Apache Shiro | Simple. Java. Security.
Apache Shiro的四大核心模块为:身份认证,授权,会话管理和加密
-
什么是身份认证
- Authentication,身份证认证,一般就是登录
-
什么是授权
- Authorization,给用户分配角色或者访问某些资源的权限
-
什么是会话管理
- Session Management, 用户的会话管理员,多数情况下是web session
-
什么是加密
- Cryptography, 数据加解密,比如密码加解密等
Shiro中一些常用的api
-
Subject
- 我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
-
SecurityManager
- 安全管理器,Subject的认证和授权都要在安全管理器下进行
-
Authenticator
- 认证器,主要负责Subject的认证
-
Realm
- 数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信
-
Authorizer
- 授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
-
Cryptography
- 加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
-
Cache Manager
- 缓存管理器,比如认证或授权信,通过缓存进行管理,提高性能
二、使用实践:Springboot2.x整合 Apache Shiro
-
整合Shiro相关jar包
- <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
在项目实际使用中,需要自定义realm
-
realm作用:Shiro 从 Realm 获取安全数据(存放在数据库中的用户及权限数据,查询只有封装到realm类中的认证对象信和授权对象信中)
-
默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm
-
两个概念
- principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
- credential:凭证, 一般就是密码
- 所以一般我们说 principal + credential 就账号 + 密码
-
开发中,往往是自定义realm , 即继承 AuthorizingRealm抽象类,需要覆写两个方法;分别是用于认证的方法和授权的方法
或者:
public class ShiroRealm extends AuthorizingRealm{ private static final Logger logger = LoggerFactory.getLogger(ShiroRealm.class); private static final String CURRENT_USER = "CURRENT_USER"; @Autowired private IUserService userService; //@Autowired //private IMemberService memberService; // 权限 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String userId = ""; Subject currentUser = SecurityUtils.getSubject(); if (null != currentUser) { try { Session session = currentUser.getSession(); if (null != session) { userId = (String) session.getAttribute(CURRENT_USER); } } catch (InvalidSessionException e) { logger.error(e.toString()); } } long uId = 0; uId = StringUtils.isEmpty(userId) ? uId :Long.parseLong(userId); UserVO sysUser = userService.getUserRole(uId); info.addRoles(sysUser.getRolesName()); Map<String,Object> map = new HashMap<String,Object>(); map.put("ids", sysUser.getRolesId()); List<Map<String,Object>> list = userService.getRolePrivilege(map); Set<String> set = new HashSet<String>(); for(Map<String,Object> lt :list){ set.add(String.valueOf(lt.get("privilegeName"))); } info.addStringPermissions(set); info.addStringPermission("user"); return info; } // 登录验证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token=(UsernamePasswordToken) authcToken; logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); String loginName = token.getUsername(); long userId = 0; userId = StringUtils.isEmpty(loginName) ? userId :Long.parseLong(loginName); StringBuilder sb = new StringBuilder(100); for (int i = 0; i < token.getPassword().length; i++) { sb.append(token.getPassword()[i]); } try { UserVO user = userService.getUserRole(userId); if(user != null){ if (user.getPassword().equals(sb.toString())) { Subject currentUser = SecurityUtils.getSubject(); logger.info("当前Subject为:"+currentUser); if (null != currentUser) { Session session = currentUser.getSession(); logger.info("当前session为:"+session); if (null != session) { session.setAttribute(CURRENT_USER, userId); logger.info("登录验证成功:"+userId); } } //saveSession(user.getUserId()); AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUserId(), user.getPassword(), user.getUserName()); return authcInfo; } } } catch (Exception e) { e.printStackTrace(); } return null; }以及shiro的其他相关配置,可以在配置类中进行配置:
import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ System.out.println("执行 ShiroFilterFactoryBean.shiroFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //需要登录的接口,如果访问某个接口,需要登录却没登录,则调用此接口(如果不是前后端分离,则跳转页面) shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //登录成功,跳转url,如果前后端分离,则没这个调用 shiroFilterFactoryBean.setSuccessUrl("/"); //没有权限,未授权就会调用此方法, 先验证登录-》再验证是否有权限 shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); //设置自定义filter Map<String,Filter> filterMap = new LinkedHashMap<>(); filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filterMap); //拦截器路径,坑一,部分路径无法进行拦截,时有时无;因为同学使用的是hashmap, 无序的,应该改为LinkedHashMap Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //退出过滤器 filterChainDefinitionMap.put("/logout","logout"); //匿名可以访问,也是就游客模式 filterChainDefinitionMap.put("/pub/**","anon"); //登录用户才可以访问 filterChainDefinitionMap.put("/authc/**","authc"); //管理员角色才可以访问 filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]"); //有编辑权限才可以访问 filterChainDefinitionMap.put("/video/update","perms[video_update]"); //坑二: 过滤链是顺序执行,从上而下,一般讲/** 放到最下面 //authc : url定义必须通过认证才可以访问 //anon : url可以匿名访问 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //如果不是前后端分离,则不必设置下面的sessionManager securityManager.setSessionManager(sessionManager()); //使用自定义的cacheManager securityManager.setCacheManager(cacheManager()); //设置realm(推荐放到最后,不然某些情况会不生效) securityManager.setRealm(customRealm()); return securityManager; } /** * 自定义realm * @return */ @Bean public CustomRealm customRealm(){ CustomRealm customRealm = new CustomRealm(); customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return customRealm; } /** * 密码加解密规则 * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //设置散列算法:这里使用的MD5算法 credentialsMatcher.setHashAlgorithmName("md5"); //散列次数,好比散列2次,相当于md5(md5(xxxx)) credentialsMatcher.setHashIterations(2); return credentialsMatcher; } //自定义sessionManager @Bean public SessionManager sessionManager(){ CustomSessionManager customSessionManager = new CustomSessionManager(); //超时时间,默认 30分钟,会话超时;方法里面的单位是毫秒 //customSessionManager.setGlobalSessionTimeout(20000); //配置session持久化 customSessionManager.setSessionDAO(redisSessionDAO()); return customSessionManager; } /** * 配置redisManager * */ public RedisManager getRedisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost("localhost"); redisManager.setPort(6379); return redisManager; } /** * 配置具体cache实现类 * @return */ public RedisCacheManager cacheManager(){ RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(getRedisManager()); //设置过期时间,单位是秒,20s redisCacheManager.setExpire(20); return redisCacheManager; } /** * 自定义session持久化 * @return */ public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(getRedisManager()); //设置sessionid生成器 redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator()); return redisSessionDAO; } /** * 管理shiro一些bean的生命周期 即bean初始化 与销毁 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * api controller 层面 * 加入注解的使用,不加入这个AOP,则shiro的注解不生效(shiro的注解 例如 @RequiresGuest,@RequiresRoles,@RequiresPermissions等) * * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } /** * 用来扫描上下文寻找所有的Advistor(通知器), * 将符合条件的Advisor应用到切入点的Bean中,需要在LifecycleBeanPostProcessor创建后才可以创建 * @return */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setUsePrefix(true); return defaultAdvisorAutoProxyCreator; } }shiro整合redis做缓存时,需要加上相关依赖
<!-- shiro+redis缓存插件 --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>或者也可以使用ehcache做本地缓存,则加入ehcache相关依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency>因为现在基本上都是web开发,所以依赖要加上:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency>在项目resources目录下添加一个shiro配置xml文件(ehcache-shiro.xml)
<?xml version="1.0" encoding="UTF-8"?> <!-- 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:内存数量最大时是否清除 --> <ehcache xmlns:xsi="www.w3/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache/ehcache.xsd" updateCheck="false" name="shiroCache"> <diskStore path="java.io.tmpdir/jeecms/shiro" /> <defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="1000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> </ehcache>在shiro配置类中,配置缓存bean方法时,替换为:
@Bean(name = "shiroEhcacheManager") public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return em; }更详细的用法可以参考官网中与spring框架整合的案例:Apache Shiro | Simple. Java. Security.
至于shiro配置类中配置的密码加解密规则,配置了密码解密规则,算法,两次md5,那么在散列用户密码的时候,用法:
String hashName = "md5"; String pwd = "123"; SimpleHash simpleHash = new SimpleHash(hashName, pwd, null, 2); // 原始密码散列之后的值:d022646351048ac0ba397d12dfafa304 String result = simpleHash.toString();在用户表(user表)中插入散列之后的密码
还有一个自定义的filter
import org.apache.shiro.subject.Subject; import org.apache.shiro.util.CollectionUtils; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; import java.util.Set; /** * 自定义filter */ public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter { @Override @SuppressWarnings({"unchecked"}) public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); //获取当前访问路径所需要的角色集合 String[] rolesArray = (String[]) mappedValue; //没有角色限制,可以直接访问 if (rolesArray == null || rolesArray.length == 0) { //no roles specified, so nothing to check - allow access. return true; } Set<String> roles = CollectionUtils.asSet(rolesArray); //当前subject是roles 中的任意一个,则有权限访问 for(String role : roles){ if(subject.hasRole(role)){ return true; } } return false; } }也可以自定义生成sessionID
import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.SessionIdGenerator; import java.io.Serializable; import java.util.UUID; /** * 自定义sesionid生成 */ public class CustomSessionIdGenerator implements SessionIdGenerator { @Override public Serializable generateId(Session session) { return UUID.randomUUID().toString().replace("-",""); } }版权声明:本文标题:shiro安全框架 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686825295a107357.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论