基本使用
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份
- Authorization:权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作(如:验证某个用户是否拥有某个角色)
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中会话可以是普通 JavaSE 环境,也可以是 Web 环境的
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
- Web Support:Web支持,可以非常容易的集成到 Web 环境
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
- Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
- Testing:提供测试支持
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
- Remember Me:记住我,即一次登录后,下次再来的话不用登录
依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.9.0</version> </dependency>
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
|
登录认证
概念
身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明(在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而验证用户身份)
principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可
注意:一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/ 邮箱/手机号
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等(最常见的principals和credentials组合就是用户名/密码)
基本流程
1、收集用户身份/凭证
2、调用 Subject.login
进行登录,如果失败就根据异常提示用户错误信息
3、创建自定义的 Realm
类,继承 org.apache.shiro.realm.AuthenticatingRealm
类,实现 doGetAuthenticationInfo()
方法
先在classpath下创建了shiro.ini文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3"); try { subject.login(token); System.out.println("登录成功"); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用户不存在"); } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("密码错误"); } catch (AuthenticationException e) { e.printStackTrace(); }
|
(1)首先调用 Subject.login(token)
进行登录,其会自动委托给 SecurityManager
(2)SecurityManager
负责真正的身份验证逻辑;它会委托给 Authenticator
进行身份
验证;
(3)Authenticator
才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此
处可以自定义插入自己的实现;
(4)Authenticator
可能会委托给相应的 AuthenticationStrategy
进 行多 Realm 身份
验证,默认 ModularRealmAuthenticator
会调用 AuthenticationStrategy
进行多 Realm
身份验证;
(5)Authenticator
会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如
果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序
及策略进行访问
角色授权
概念
1、授权:也叫访问控制,即在应用中控制谁访问哪些资源
2、主体:访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
3、资源:在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问
4、权限:代表了用户有没有操作某个资源的权利。
5、角色:权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。不同的角色拥有一组不同的权限
示例
在ini文件中先进行定义
1 2 3 4 5 6 7 8
| boolean hasRole = subject.hasRole("role1"); System.out.println("是否拥有此角色 = " + hasRole);
boolean permitted = subject.isPermitted("user:insert"); System.out.println("是否拥有此权限 = " + permitted);
subject.checkPermission("user:select");
|
加密示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static void main(String[] args) { String password = "z3"; Md5Hash md5Hash = new Md5Hash(password); System.out.println("md5加密 = " + md5Hash.toHex()); Md5Hash md5Hash2 = new Md5Hash(password,"salt"); System.out.println("带盐的md5加密 = " + md5Hash2.toHex()); Md5Hash md5Hash3 = new Md5Hash(password,"salt",3); System.out.println("md5带盐的3次加密 = " + md5Hash3.toHex()); SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3); System.out.println("父类带盐的3次加密 = " + simpleHash.toHex()); }
|
自定义登录认证
Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义Realm
通过继承AuthenticatingRealm类,重写doGetAuthenticationInfo方法进行自定义操作
注意:shiro的login方法底层会调用该类的认证方法进行认证,需要配置自定义的realm生效,在ini文件中配置,在Springboot中配置。该方法只是获取进行对比的信息,认证逻辑还是按照shiro底层认证逻辑完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class MyRealm extends AuthenticatingRealm { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String principal = authenticationToken.getPrincipal().toString(); String password = new String((char[])authenticationToken.getCredentials()); System.out.println("认证用户信息:"+principal+"---"+password);
if(principal.equals("zhangsan")){ String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
AuthenticationInfo info = new SimpleAuthenticationInfo( authenticationToken.getPrincipal(), pwdInfo, ByteSource.Util.bytes("salt"), authenticationToken.getPrincipal().toString() ); return info; } return null; } }
|
结合springboot实现登录认证
1、首先创建出mapper,entity,service,并设计好数据库
2、创建controller,以便进行测试
3、创建自定义Realm
1)继承AuthorizingRealm类,重写doGetAuthorizationInfo方法(一个是自定义授权方法,一个是自定义登录认证方法)
2)在自定义登录认证方法中获取用户身份信息
3)从数据库中获取用户信息,并将数据封装到SimpleAuthenticationInfo类中
4)最后返回该类即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Component public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String name = token.getPrincipal().toString(); User user = userService.getUserInfoByName(name); if(user!=null){ AuthenticationInfo info = new SimpleAuthenticationInfo( token.getPrincipal(), user.getPwd(), ByteSource.Util.bytes("salt"), token.getPrincipal().toString() ); return info; } return null; } }
|
4、创建config配置类,配置 SecurityManager
1)创建defaultWebSecurityManager对象
2)创建加密对象,设置属性
3)将加密对象存储到 myRealm 中
4)将MyRealm存储到defaultWebSecurityManager对象中并返回
5、配置 Shiro 内置过滤器拦截范围
1)创建DefaultShiroFilterChainDefinition对象
2)调用addPathDefinition方法设置不认证就可以进行访问的资源
3)调用addPathDefinition方法设置需要进行登录认证的拦截范围
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Configuration public class ShiroConfig { @Autowired private MyRealm myRealm; @Bean public DefaultWebSecurityManager defaultWebSecurityManager(){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(3); myRealm.setCredentialsMatcher(matcher); defaultWebSecurityManager.setRealm(myRealm); return defaultWebSecurityManager; } @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); definition.addPathDefinition("/myController/userLogin","anon"); definition.addPathDefinition("/login","anon"); definition.addPathDefinition("/**","authc"); return definition; } }
|
多个realm实现原理(*)
配置多个realm,Shiro会使用内部的 AuthenticationStrategy 组件判断认证是成功还是失败
AuthenticationStrategy 在身份验证尝试中被询问4次
1、在所有 Realm 被调用之前
2、在调用 Realm 的 getAuthenticationInfo 方法之前
3、在调用 Realm 的 getAuthenticationInfo 方法之后
4、在所有 Realm 被调用之后
Remember-Me功能
1、在登录接口额外记录一个参数
2、设置cookie 属性
1 2 3 4 5 6 7 8 9
| public SimpleCookie rememberMeCookie(){ SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(30*24*60*60); return cookie; }
|
3、创建 Shiro 的 cookie 管理对象
1 2 3 4 5 6
| public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); cookieRememberMeManager.setCipherKey("1234567890987654".getBytes()); return cookieRememberMeManager; }
|
4、在config配置SecurityManager时,设置 rememberMe
5、添加存在用户的过滤器
登录认证后登出
授权,角色登录
后端接口服务注解
@RequiresAuthentication 验证用户是否登录,等同于方法subject.isAuthenticated()
@RequiresUser 验证用户是否被记忆: 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true
@RequiresGuest 验证是否是一个guest的请求,是否是游客的请求 此时subject.getPrincipal()为null
@RequiresRoles 验证subject是否有相应角色,有角色访问方法,没有则会抛出异常AuthorizationException。
@RequiresPermissions 验证subject是否有相应权限,访问方法,没有则会抛出异常AuthorizationException
角色认证
1 2 3 4 5 6 7 8
| @RequiresRoles("admin") @GetMapping("userLoginRoles") @ResponseBody public String userLoginRoles(){ System.out.println("登录认证验证角色"); return "验证角色成功"; }
|
在自定义授权方法内,存储当前登录用户的权限和角色,此时再次进行权限认证时,认证成功
权限认证
创建数据库权限表和角色权限映射表
修改Myrealm类的自定义授权方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("进入自定义授权方法"); String principal = principalCollection.getPrimaryPrincipal().toString(); List<String> roles = userService.getUserRoleInfo(principal); System.out.println("当前用户角色信息:"+roles); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); return info; }
|
添加controller的认证验证权限
1 2 3 4 5 6 7
| @RequiresPermissions("user:delete") @GetMapping("userPermissions") @ResponseBody public String userLoginPermissions(){ System.out.println("登录认证验证权限"); return "验证权限成功"; }
|
Shiro整合EhCache
1、引入依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
|
2、添加相关的配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?xml version="1.0" encoding="UTF-8"?> <ehcache name="ehcache" updateCheck="false"> <diskStore path="java.io.tmpdir"/> <defaultCache maxEntriesLocalHeap="1000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="false"> </defaultCache> <cache name="loginRolePsCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"/> </ehcache>
|
3、修改配置类 ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public EhCacheManager getEhCacheManager(){ EhCacheManager ehCacheManager = new EhCacheManager(); InputStream is = null; try { is = ResourceUtils.getInputStreamForPath( "classpath:ehcache/ehcache-shiro.xml"); } catch (IOException e) { e.printStackTrace(); } CacheManager cacheManager = new CacheManager(is); ehCacheManager.setCacheManager(cacheManager); return ehCacheManager; }
|
并在配置SecurityManager中,设置缓存管理器
会话管理
SessionManager
会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中 在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话
会话管理实现
SessionManager由SecurityManager管理。Shiro提供了三种实现
DefaultSessionManager:用于JavaSE环境
ServletContainerSessionManager:用于web环境,直接使用Servlet容器的会话
DefaultWebSessionManager:用于web环境,自己维护会话(不使用Servlet容器的 会话管理)
获取Session的方式
1 2
| Session session = SecurityUtils.getSubject().getSession(); session.setAttribute(“key”,”value”)
|
注意:Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成 ShiroHttpServletRequest
SecurityManager和SessionManager会话管理器决定session来源于ServletRequest还是由Shiro管理的会话
无论是通过 request.getSession 或 subject.getSession 获取到 session,操作 session,两者都是等价的