Shiro可以帮助我们完成认证、授权、加密、会话管理、与Web集成、缓存等
Shiro的使用方法
基本功能模块
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。
- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话 中;会话可以是普通JavaSE环境的,也可以是如Web环境的。
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
- Web Support:Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
- Concurrency:Apache Shiro 利用它的并发特性来支持多线程应用程序。
- Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
- “Run As”:一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用
- “Remember Me”:记住我。
内部结构
- Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
- SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
- Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
- Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
- Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
- SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据; 给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
- SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
- CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
- Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
使用方法
导入shiro坐标
认证与授权
将安全数据存入到shiro进行保管,登陆认证的主要目的,比较用户名和密码是否与数据库中的一致
shiro登陆与传统登陆:
1.传统登录
前端发送登录请求 => 接口部分获取用户名密码 => 程序员在接口部分手动控制
2.shiro登录
前端发送登录请求 => 接口部分获取用户名密码 => 通过subject.login => realm域的认证方法
授权:即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。授权的主要目的就是根据认证数据获取到用户的权限信息
认证流程
- 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实 现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问
授权流程
- 首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托 给Authorizer;
- Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示授权失败。
测试认证与授权:
publicclassShiroTest{privateSecurityManager securityManager;@Beforepublicvoidinit(){//1.根据配置文件创建SecurityManagerFactoryFactory<SecurityManager> factory =newIniSecurityManagerFactory("classpath:shiro-test.ini");//2.通过工厂获取SecurityManagerSecurityManager securityManager = factory.getInstance();//3.将SecurityManager绑定到当前运行环境SecurityUtils.setSecurityManager(securityManager);}@TestpublicvoidtestLogin(){Subject subject =SecurityUtils.getSubject();String username ="zhangsan";String password ="123456";UsernamePasswordToken token =newUsernamePasswordToken(username,password);//执行login-->realm域中的认证方法
subject.login(token);//授权:-->realm域中的授权方法System.out.println(subject.hasRole("role1"));System.out.println(subject.isPermitted("user:save"));}}
基于配置的授权
//配置请求连接过滤器配置//匿名访问(所有人员可以使用)
filterMap.put("/user/home","anon");//具有指定权限访问
filterMap.put("/user/find","perms[user-find]");//认证之后访问(登录之后可以访问)
filterMap.put("/user/**","authc");//具有指定角色可以访问
filterMap.put("/user/**","roles[系统管理员]");
基于配置的方式进行授权,一旦操作用户不具备操作权限,目标地址不会被执行。会跳转到指定的url连接地址。所以需要在连接地址中更加友好的处理未授权的信息提示。
基于注解的授权
@RequiresPermissions(value = “权限名”)配置到方法上,表明执行此方法必须具有指定的权限
@RequiresRoles(value = “角色名”) 配置到方法上,表明执行此方法必须具有指定的角色
一旦操作用户不具备操作权限,目标方法不会被执行,而且会抛出AuthorizationException 异常
Realm域
Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源
自定义realms对象类继承AuthorizingRealm
重写方法
- doGetAuthorizationInfo:授权 - 获取到用户的授权数据(用户的权限数据)- 构造认证数据
- doGetAuthenticationInfo:认证 - 获取登陆uptoken- 根据用户名密码登录,将用户数据保存(安全数据)
publicclassPermissionRealmextendsAuthorizingRealm{/**
* 自定义realm名称
*/publicvoidsetName(String name){super.setName("permissionRealm");}//授权:授权的主要目的就是根据认证数据获取到用户的权限信息/**
* principalCollection:包含了所有已认证的安全数据
* AuthorizationInfoInfo:授权数据
*/protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection){System.out.println("执行授权方法");//1.获取安全数据 username,用户idString username =(String)principalCollection.getPrimaryPrincipal();//构造认证数据//2.根据id或者名称查询用户//3.查询用户的角色和权限信息List<String> perms =newArrayList<>();
perms.add("user:save");
perms.add("user:update");List<String> roles =newArrayList<>();
roles.add("role1");
roles.add("role2");//4.构造返回SimpleAuthorizationInfo info =newSimpleAuthorizationInfo();//设置权限集合,将查询的权限数据保存到simpleAuthorizationInfo
info.addStringPermissions(perms);//设置角色集合,将查询的角色数据保存到simpleAuthorizationInfo
info.addRoles(roles);return info;}//认证:认证的主要目的,比较用户名和密码是否与数据库中的一致//将安全数据存入到shiro进行保管//参数:authenticationToken登录构造的usernamepasswordtokenprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken)throwsAuthenticationException{System.out.println("执行认证方法");//1.构造uptokenUsernamePasswordToken upToken =(UsernamePasswordToken) authenticationToken;//2.获取输入的用户名密码String username = upToken.getUsername();String password =newString(upToken.getPassword());//3.根据用户名查询数据库,正式系统查询//4.比较密码和数据库中的密码是否一致(密码可能需要加密)if("123456".equals(password)){//5.如果成功,向shiro存入安全数据SimpleAuthenticationInfo info =newSimpleAuthenticationInfo(username,password,getName());//1.安全数据,2.密码。3。当前realm域名称return info;}else{//6.失败,抛出异常或返回nullthrownewRuntimeException("用户名或密码错误");}}}
SecurityManager
用于协调内部的多个组件完成全部认证授权的过程。通过调用realm
完成认证与登录。
配置ShiroConfiguration类
@ConfigurationpublicclassShiroConfiguration{//1.创建realm@BeanpublicCustomRealmgetRealm(){returnnewCustomRealm();}//2.创建安全管理器@BeanpublicSecurityManagergetSecurityManager(CustomRealm realm){//使用默认的安全管理器DefaultWebSecurityManager securityManager =newDefaultWebSecurityManager();//将自定义的realm交给安全管理器统一调度管理
securityManager.setRealm(realm);return securityManager;}//开启对shior注解的支持@BeanpublicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor advisor =newAuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);return advisor;}}
过滤器
Filter解释anon无参,开放权限,可以理解为匿名用户或游客authc无参,需要认证logout无参,注销,执行后会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的urlauthcBasic无参,表示 httpBasic 认证user无参,表示必须存在用户,当登入操作时不做检查ssl无参,表示安全的URL请求,协议为 httpsperms[user]参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user,admin”],当有多个参数时必须每个参数都通过才算通过roles[admin]参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个参数都通过才算通过rest[user]根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等port[8081]当请求的URL端口不是8081时,跳转到当前访问主机HOST的8081端口
anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url )
在ShiroConfiguration中配置shiro的过滤器工厂:
/**
* 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
*
*/@BeanpublicShiroFilterFactoryBeanshiroFilter(SecurityManager securityManager){//1.创建过滤器工厂ShiroFilterFactoryBean filterFactory =newShiroFilterFactoryBean();//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);//3.通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url//4.设置过滤器集合/**
* 设置所有的过滤器:有顺序map
* key = 拦截的url地址
* value = 过滤器类型
*
*/Map<String,String> filterMap =newLinkedHashMap<>();
filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问//设置过滤器
filterFactory.setFilterChainDefinitionMap(filterMap);return filterFactory;}
会话管理SessionManager
shiro提供的会话,不依赖于任何底层容器,可以独立使用。管理所有Subject的session包括创建、维护、删除、失效、验证等工作。SessionManager由SecurityManager管理
三种默认实现:
- DefaultSessionManager:用于JavaSE环境
- ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。
- DefaultWebSessionManager:用于Web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)。
在web程序中,通过shiro的Subject.login()方法登录成功后,用户的认证信息实际上是保存在HttpSession中。
自定义会话管理器
会话管理器可以指定sessionId的生成以及获取方式
一般结合redis进行会话管理 -->自定义shiro会话管理器extends DefaultWebSessionManager–>自定义SessionDao实现,指定Redis配置
- 头信息中具有sessionid - 请求头:Authorization: sessionid- 指定sessionId的获取方式
publicclassCustomSessionManagerextendsDefaultWebSessionManager{protectedSerializablegetSessionId(ServletRequest request,ServletResponse response){//获取请求头Authorization中的数据String id =WebUtils.toHttp(request).getHeader("Authorization");if(StringUtils.isEmpty(id)){//如果没有携带,生成新的sessionIdreturnsuper.getSessionId(request,response);}else{//返回sessionId;
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,"header");//sessionId具体是什么
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);return id;}}}
配置基于redis的会话管理
在ShiroConfiguration配置类中添加Redismanager, RedisCasheManager(缓存机制), RedisSessionDao, 自定义的会话管理DefaultWebSessionManager
@Value("${spring.redis.host}")privateString host;@Value("${spring.redis.port}")privateint port;@Value("${spring.redis.password}")privateString password;/**
* redis的控制器,操作redis
*/publicRedisManagerredisManager(){RedisManager redisManager =newRedisManager();
redisManager.setHost(host);
redisManager.setPort(port);//redisManager.setPassword(password);return redisManager;}/**
* sessionDao
*/publicRedisSessionDAOredisSessionDAO(){RedisSessionDAO sessionDAO =newRedisSessionDAO();
sessionDAO.setRedisManager(redisManager());return sessionDAO;}/**
* 会话管理器
*/publicDefaultWebSessionManagersessionManager(){CustomSessionManager sessionManager =newCustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}/**
* 缓存管理器
*/publicRedisCacheManagercacheManager(){RedisCacheManager redisCacheManager =newRedisCacheManager();
redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}
SecurityManager中设置自定义session管理
@BeanpublicSecurityManagergetSecurityManager(CustomRealm realm){//使用默认的安全管理器DefaultWebSecurityManager securityManager =newDefaultWebSecurityManager();
securityManager.setRealm(realm);//将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());return securityManager;}
使用细节
- 存入redis的数据对象类需要实现AuthCachePrincipali接口
- 在多个微服务中使用,配置公共的未认证未授权的Controller(需要跨域@CrossOrigin)–>作为公共错误跳转
- 认证授权分离–公共模块下创建授权realm(只处理授权数据),认证方法在登陆模块补全–>配置用户登陆的realm域继承公共realm域 - 获取用户数据- 构造安全数据- 构造方法
SimpleAuthenticationInfo( Object principal, Object credentials, String realmName )
- 在shiro的会话管理中,可以使用请求头中的内容作为sessionid
String id =WebUtils.toHttp(request).getHeader(AUTHORIZATION);if(StringUtils.isEmpty(id)){//如果没有携带id参数则按照父类的方式在cookie进行获取returnsuper.getSessionId(request, response);}else{
id = id.replace("Bearer ","");//如果请求头中有 authToken 则其值为sessionId
从redis获取session数据
Subject subject = SecurityUtils.getSubject();
- 获取所有的安全数据集合`PrincipalCollection principals = subject.getPrincipals();
- 获取安全数据
(ProfileResult)principals.getPrimaryPrincipal();
版权归原作者 我爱松子鱼 所有, 如有侵权,请联系我们删除。