Shiro学习笔记 您所在的位置:网站首页 shiro工作流程 Shiro学习笔记

Shiro学习笔记

#Shiro学习笔记| 来源: 网络整理| 查看: 265

Shiro教学视频

源码

文章目录 1. 简介功能简介Shiro 架构Shiro 架构(Shiro外部来看)Shiro 架构(Shiro内部来看) 2. HelloWorldshiro.ini配置文件解析Quickstart.java解析运行结果 3. 集成Spring配置web.xml配置spring-servlet.xml配置applicationContext.xml 4. Shiro工作流程5. DelegatingFilterProxy6. 权限URL配置细节URL 匹配模式Shiro中默认的过滤器URL 匹配顺序 7. 认证思路分析8. 实现认证流程9. 实现认证Realm10. 密码的比对11. 密码的MD5加密12. 密码的MD5盐值加密13. 多Realm验证14. 认证策略15. 把realms配置给SecurityManager16. 权限配置Shiro 支持三种方式的授权默认拦截器身份验证相关的拦截器授权相关的拦截器其他的拦截器 实操 17. 授权流程分析18. 多Realm授权的通过标准19. 实现授权Realm20. 标签21. 权限注解22. 从数据表中初始化资源和权限23. 会话管理Session会话相关的 API 24. SessionDao25. 缓存CacheManagerAware 接口Realm 缓存Session 缓存 26. 认证和记住我的区别27. 实现Remember me

1. 简介 Apache Shiro 是 Java 的一个安全(权限)框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以完成:认证、授权、加密、会话管理、与Web集成、缓存等。Shiro官网 功能简介

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中; 会话可以是普通JavaSE环境,也可以是 Web 环境的;Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;Web Support:Web 支持,可以非常容易的集成到Web环境;Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;Concurrency: Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能 把权限自动传播过去;Testing:提供测试支持;Run As: 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;Remember Me: 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

authentication /ɔːˌθentɪˈkeɪʃn/n. 身份验证;认证;鉴定

authorization /ˌɔːθərəˈzeɪʃn/n. 批准;授权;批准书;授权书

Shiro 架构 Shiro 架构(Shiro外部来看)

从外部来看Shiro,即从应用程序角度的来观察如何使用 Shiro 完成工作

Subject:应用代码直接交互的对象是Subject,也就是说 Shiro 的对外API核心就是Subject。Subject代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等; 与 Subject 的所有交互都会委托给SecurityManager;Subject 其实是一个门面, SecurityManager 才是实际的执行者;SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色Realm:Shiro从Realm(领域、场所) 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource Shiro 架构(Shiro内部来看)

2. HelloWorld 新键Maven Web工程。pom.xml加入依赖: org.apache.shiro : shiro-all : 1.3.2org.slf4j : slf4j-log4j12 : 1.8.0-alpha2 添加配置文件: shiro.ini Shiro的配置文件log4j.properties 日志配置 添加Shiro的HelloWorld类——Quickstart.java shiro.ini配置文件解析

shiro.ini配置文件主要内容为:

用户=密码, 角色角色=角色对应的权限 # ----------------------------------------------------------------------------- # 用户=密码, 角色 # Users and their assigned roles ----------------------------------------------------------------------------- [users] # 1. user 'root' with password 'secret' and the 'admin' role root = secret, admin # 2. user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # 3. user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;)), and role 'president' presidentskroob = 12345, president # 4. user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # 5. user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # 角色以及它的权限 ----------------------------------------------------------------------------- [roles] # 1. 'admin' role has all permissions, indicated by the wildcard '*' admin = * # 2. The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # 3. The 'goodguy' role is allowed to 'delete' (action) the user (type) with # license plate 'zhangsan' (instance specific id) goodguy = user:delete:zhangsan Quickstart.java解析

从Quickstart.java中,大概可看出如何使用Shiro进行认证、授权等安全操作。

public class Quickstart { public static void main(String[] args) { //创建并安全管理器,需要用到上述的shiro.ini配置文件 Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //获取当前的Subject,这里Subject可理解为即将登陆的用户 Subject currentUser = SecurityUtils.getSubject(); // 测试使用Session读写键值对,这个Session无需Web容器就可使用 Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("---> Retrieved the correct value! [" + value + "]"); } // 测试当前的用户是否已经被认证,即是否已经登录 if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); // rememberme token.setRememberMe(true); try { //执行登录. currentUser.login(token); } // 若没有指定的账户,则抛出 UnknownAccountException catch (UnknownAccountException uae) { log.info("----> There is no user with username of " + token.getPrincipal()); return; } // 若账户存在,但密码不匹配,则抛出 IncorrectCredentialsException 异常 catch (IncorrectCredentialsException ice) { log.info("----> Password for account " + token.getPrincipal() + " was incorrect!"); return; } // 用户被锁定的异常 LockedAccountException catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // 所有认证时异常的父类AuthenticationException,也就是说LockedAccountException、IncorrectCredentialsException等继承该父类 catch (AuthenticationException ae) { //unexpected condition? error? } } //print their identifying principal (in this case, a username): log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully."); // 测试是否有某一个角色 if (currentUser.hasRole("schwartz")) { log.info("----> May the Schwartz be with you!"); } else { log.info("----> Hello, mere mortal."); return; } // 测试用户是否具备某一个行为 if (currentUser.isPermitted("lightsaber:weild")) { log.info("----> You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //测试用户是否具备某一个更具体的行为: if (currentUser.isPermitted("user:delete:zhangsan")) { log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } // 执行登出 log.info("---->currentUser.isAuthenticated(): " + currentUser.isAuthenticated()); currentUser.logout(); log.info("---->currentUser.isAuthenticated(): " + currentUser.isAuthenticated()); System.exit(0); } }

principal [ˈprɪnsəpl] adj. 最重要的;主要的 n. 大学校长;学院院长;本金;资本;主要演员;主角

运行结果 2020-07-11 18:43:05,687 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2020-07-11 18:43:06,051 INFO [com.lun.shiro.one.Quickstart] - Retrieved the correct value! [aValue] 2020-07-11 18:43:06,054 INFO [com.lun.shiro.one.Quickstart] - User [lonestarr] logged in successfully. 2020-07-11 18:43:06,055 INFO [com.lun.shiro.one.Quickstart] - May the Schwartz be with you! 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - You may use a lightsaber ring. Use it wisely. 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. Here are the keys - have fun! 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - currentUser.isAuthenticated(): true 2020-07-11 18:43:06,059 INFO [com.lun.shiro.one.Quickstart] - currentUser.isAuthenticated(): false 3. 集成Spring 在已有的Maven工程的pom.xml加入Spring框架全家桶依赖添加配置文件 web.xmlapplicationContext.xmlspring-servlet.xml 配置web.xml

配置web.xml主要目的:

引入Spring - org.springframework.web.context.ContextLoaderListener引入SpringMVC - org.springframework.web.servlet.DispatcherServlet引入Shiro org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:applicationContext.xml spring org.springframework.web.servlet.DispatcherServlet 1 spring / shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /* Shiro 提供了与 Web 集成的支持,其通过一个ShiroFilter 入口来拦截需要安全控制的URL,然后进行相应的控制ShiroFilter 类似于如 Strut2/SpringMVC 这种web 框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini 配置文件),然后判断URL是否需要登录/权限等工作。 配置spring-servlet.xml

在spring-servlet.xml配置SpringMVC信息

配置applicationContext.xml

Shiro的配置信心主要在applicationContext.xml中。

配置 SecurityManager配置 CacheManager配置 Realm配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用配置 ShiroFilter /login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc 4. Shiro工作流程

5. DelegatingFilterProxy

在web.xml中

shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true

在applicationContext.xml中

... ... ... 6. 权限URL配置细节

[urls]部分的配置格式是:“url=拦截器[参数],拦截器[参数]”。

如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会执行其配置的拦截器。

拦截器例子如下:

anon(anonymous)拦截器表示匿名访问(即不需要登录即可访问)authc(authentication)拦截器表示需要身份认证通过后才能访问 /login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc URL 匹配模式

url 模式使用 Ant 风格模式

Ant 路径通配符支持 ?、 *、 **,注意通配符匹配不包括目录分隔符“/”

?:匹配一个字符, 如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;*:匹配零个或多个字符串, 如 /admin 将匹配 /admin、/admin123,但不匹配 /admin/1;**:匹配路径中的零个或多个路径, 如 /admin/** 将匹配 /admin/a 或 /admin/a/b Shiro中默认的过滤器

Default Filters

URL 匹配顺序

URL 权限采取第一次匹配优先的方式, 即从头开始使用第一个匹配的 url 模式对应的拦截器链。如:

/bb/**=filter1/bb/aa=filter2/**=filter3

如果请求的url是“/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。(先声夺人)

7. 认证思路分析

认证说白就是登陆

获取当前的Subject,调用SecurityUtils.getSubject()测试当前的用户是否已经被认证,即是否已经登录。调用 Subject.isAuthenticated()若没有被认证,则把用户名和密码封装为 UsernamePasswordToken 对象 创建一个表单页面把请求提交到SpringMVC的Handler获取用户名和密码. 执行登录:调用Subject的login(AuthenticationToken)方法自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro. 实际上需要继承org.apache.shiro.realm.AuthenticatingRealm类实现doGetAuthenticationInfo(AuthenticationToken)方法. 由Shiro完成对密码的比对。 8. 实现认证流程 创建表单页面login.jsp创建表单页面Controller - ShiroHandler创建Realm - ShiroRealm,从数据库中获取对应的记录,返回给 Shiro. 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类实现 doGetAuthenticationInfo(AuthenticationToken) 方法,添加账号,密码验证等相关代码。 9. 实现认证Realm 把 AuthenticationToken 转换为 UsernamePasswordToken从 UsernamePasswordToken 中来获取 username调用数据库的方法, 从数据库中查询 username 对应的用户记录若用户不存在, 则可以抛出 UnknownAccountException 异常根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.根据用户的情况, 来构建 AuthenticationInfo 对象并返回。通常使用的实现类为: SimpleAuthenticationInfo,以下信息是从数据库中获取的 principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象。credentials: 密码realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可

ShiroRealm

public class ShiroRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { System.out.println("[FirstRealm] doGetAuthenticationInfo"); //1. 把 AuthenticationToken 转换为 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2. 从 UsernamePasswordToken 中来获取 username String username = upToken.getUsername(); //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录 System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息."); //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在!"); } //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. if("monster".equals(username)){ throw new LockedAccountException("用户被锁定"); } //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //以下信息是从数据库中获取的. //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. Object principal = username; //2). credentials: 密码. Object credentials = "123456"; //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName); return info; } } 10. 密码的比对

密码的比对:通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对。

11. 密码的MD5加密

如何把一个字符串加密为 MD5

替换当前 Realm 的 credentialsMatcher 属性。直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可。

在applicationContext.xml中

既然对密码了,ShiroRealm中的部分代码稍作修改。

计算求出密码加密后的密码

public static void main(String[] args) { String hashAlgorithmName = "MD5"; Object credentials = "123456"; Object salt = null;//盐值下一节将提到 int hashIterations = 1024; Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); System.out.println(result); }

得出fc1709d0a95a6be30bc5926fdb7f22f4

然后在ShiroRealm中的

Object credentials = "123456";

改为

Object credentials = "fc1709d0a95a6be30bc5926fdb7f22f4";

便可实现密码加密,通过验证。

12. 密码的MD5盐值加密

为什么使用 MD5 盐值加密: 因为有时希望即使两个原始密码一样,加密后的密码也不一样,这样做相对安全些。

盐是日常生活中的调料。

如何做到:

在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候,需要使用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName)构造器使用ByteSource.Util.bytes()来计算盐值。盐值需要唯一:一般使用随机字符串或 user id使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);来计算盐值加密后的密码的值。

然后计算密码加密加盐的值

public static void main(String[] args) { String hashAlgorithmName = "MD5"; Object credentials = "123456"; Object salt = ByteSource.Util.bytes("user"); int hashIterations = 1024; Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); System.out.println(result); }

在ShiroRealm进行对应的修改

@Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { .... //2). credentials: 密码. Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4"; if("admin".equals(username)){ credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";/*上面main()计算得出加密加盐的值*/ }else if("user".equals(username)){ credentials = "098d2c478e9c11555ce2823231e02ec1";/*上面main()计算得出加密加盐的值*/ } //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //4). 盐值. ByteSource credentialsSalt = ByteSource.Util.bytes(username); SimpleAuthenticationInfo info = null; info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; } 13. 多Realm验证

realm /relm/ n. 领域; 场所; 王国

可设置多个Realm设置

applicationContext.xml

创建添加第二个Realm,SecondRealm

applicationContext.xml实现SecondRealm的Bean

14. 认证策略

AuthenticationStrategy 接口的默认实现:

FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可, 和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略 15. 把realms配置给SecurityManager

中的property name="realms"部分可以移动到bean id=“securityManager” 中,原因在查阅源码中,securityManager 的authenticator是ModularRealmAuthenticator类的实例,就将securityManager的realms注入authenticator当中。

这样做可以放心为下一节授权做铺垫。

16. 权限配置 授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体( Subject)、资源( Resource)、权限( Permission)、角色( Role)。主体(Subject):访问应用的用户, 在 Shiro 中使用 Subject代表该用户。用户只有授权后才允许访问相应的资源。资源(Resource):在应用中用户可以访问的 URL, 比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。 Shiro 支持三种方式的授权 编程式:通过写if/else 授权代码块完成注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成 默认拦截器

Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:

Default Filters

身份验证相关的拦截器 默认拦截器名拦截器类说明(括号里的表示默认值)authcorg.apache. shiro.web.filter.authc.FormAuthenticationFilter基于表单的拦截器;如"/**=authc" ,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam :表单提交的用户名参数名(username);passwordParam :表单提交的密码参数名(password);rememberMeParam :表单提交的密码参数名(rememberMe);loginUrl :登录页面地址(/login.jsp);successUrl :登录成功后的默认重定向地址;failureKeyAttribute :登录失败后错误信息存储key( shirologinFailure);authcBasicorg.apache. shiro.web.filter.authc.BasicHttpAuthenticationFilterBasic HTTP身份验证拦截器,主要属性 :applicationName :弹出登录框显示的信息( application) ;logoutorg.apache.shiro.web.filter.authc.LogoutFilter退出拦截器,主要属性: redirectUrl :退出成功后重定向的地址( / ) ;示例:"/logout=logout"userorg.apache.shiro.web.filter.authc.UserFilter用户拦截器,用户已经身份验证/记住我登录的都可;示例"/** =user"anonorg.apache.shiro.web.filter.authc.AnonymousFilter匿名拦截器,即不需要登录即可访问; -般用于静态资源过滤;示例:/static/** = anon" 授权相关的拦截器 默认拦截器名拦截器类说明(括号里的表示默认值)rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter角色授权拦截器,验证用户是否拥有所有角色;主要属性:loginUrl :登录页面地址(/login.jsp);unauthorizedUrl :未授权后重定向的地址;示例: “/admin/** =roles[admin]”permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例"/user/**=perms[“user:create”]"portorg.apache.shiro.web.filter.authz.PortFilter端口拦截器,主要属性: port(80) :可以通过的端口;示例"/test= port[80]如果用户访问该页面是非80 ,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrest风格拦截器,自动根据请求方法构建权限字符串( GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS =read, MKCOL=create )构建权限字符串;示例:/users=rest[user]” ,会自动拼出"user:read,user:create,user:update,user:delete"权限字符串进行权限匹配(所有都得匹配, isPermittedAll);sslorg.apache.shiro.web.filter.authz.SslFilterSSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443) ;其他和port拦截器一样; 其他的拦截器 默认拦截器名拦截器类说明(括号里的表示默认值)noSessionCreationorg.apache.shiro.web.filter.session. NoSessionCreationFilter不创建会话拦截器,调用subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出 实操

新建两页面

admin.jsp - 只可角色admin访问的user.jsp - 只可角色user访问的

在applicationContext.xml中进行配置

/login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc

在ShiroRealm设置realmName字符串是用来设置角色

public class ShiroRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //... info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);//realmName是设置角色的 return info; } } 17. 授权流程分析 授权需要继承 AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的 doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法. public class TestRealm extends AuthorizingRealm { //用于授权的方法. @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; } //用于认证的方法 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // TODO Auto-generated method stub return null; } }

调用详细流程可以通过Debug Quickstart的currentUser.hasRole("schwartz")进行了解

18. 多Realm授权的通过标准

调用详细流程可以通过Debug Quickstart的currentUser.hasRole("schwartz")进行了解

19. 实现授权Realm

ShiroRealm

public class ShiroRealm extends AuthorizingRealm { //... //授权会被 shiro 回调的方法 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //1. 从 PrincipalCollection 中来获取登录用户的信息 Object principal = principals.getPrimaryPrincipal(); //2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库) Set roles = new HashSet(); roles.add("user"); if("admin".equals(principal)){ roles.add("admin"); } //3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); //4. 返回 SimpleAuthorizationInfo 对象. return info; } } 20. 标签

Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。

在JSP文件中引入时

guest 标签:用户没有身份验证时显示相应信息,即游客访问信息:

欢迎游客访问

user 标签:用户已经经过认证/记住我登录后显示相应的信息。

欢迎

authenticated 标签:用户已经身份验证通过,即Subject.login登录成功, 不是记住我登录的

notAuthenticated 标签: 用户未进行身份验证, 即没有调用Subject.login进行登录, 包括记住我自动登录的也属于未进行身份验证。

pincipal标签: 显示用户身份信息,默认调用Subject.getPrincipal() 获取, 即 Primary Principal。

hasRole 标签:如果当前 Subject 有角色将显示 body 体内容:

Admin Page

hasAnyRoles 标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容

lacksRole:如果当前 Subject 没有角色将显示 body 体内容

hasPermission: 如果当前 Subject 有权限将显示 body 体内容

lacksPermission: 如果当前Subject没有权限将显示body体内容。

21. 权限注解 @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证; 即 Subject. isAuthenticated() 返回 true@RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。@RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):表示当前 Subject 需要角色 admin 和user@RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示当前 Subject 需要权限 user:a 或user:b。

ShiroService

public class ShiroService { @RequiresRoles({"admin"})//这里使用到权限注解,这里RequiresRoles要用到admin角色权限 public void testMethod(){ System.out.println("testMethod, time: " + new Date()); Session session = SecurityUtils.getSubject().getSession(); Object val = session.getAttribute("key"); System.out.println("Service SessionVal: " + val); } }

在applicaContext.xml实现Bean

@Controller @RequestMapping("/shiro") public class ShiroHandler { @Autowired private ShiroService shiroService; @RequestMapping("/testShiroAnnotation") public String testShiroAnnotation(HttpSession session){ session.setAttribute("key", "value12345"); shiroService.testMethod();//该方法调用需要验证权限,用到admin角色权限,否则,就会报错 return "redirect:/list.jsp"; } //... } 22. 从数据表中初始化资源和权限

在applicationContext.xml硬编码分配权限角色,这不太好。通常情况下,是需要从数据库中读取权限角色的。

将property name="filterChainDefinitions"注释掉,添加property name=“filterChainDefinitionMap” ref=“filterChainDefinitionMap”

... ...

然后,在applicationContext.xml中注册工厂方法Bean:

然后实现工厂类FilterChainDefinitionMapBuilder.java

public class FilterChainDefinitionMapBuilder { public LinkedHashMap buildFilterChainDefinitionMap(){ LinkedHashMap map = new LinkedHashMap(); //模拟从数据库中取数据 map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/user.jsp", "authc,roles[user]"); map.put("/admin.jsp", "authc,roles[admin]"); map.put("/list.jsp", "user"); map.put("/**", "authc"); return map; } } 23. 会话管理

Shiro 提供了完整的企业级会话管理功能, 不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、 SSO 单点登录的支持等特性。

Session会话相关的 API Subject.getSession():即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建 Session 对象会创建一个; Subject.getSession(false),如果当前没有创建 Session 则返回nullsession.getId():获取当前会话的唯一标识session.getHost():获取当前Subject的主机地址session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。session.touch() & session.stop():更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中, 调用 HttpSession. invalidate()也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会话session.setAttribute(key, val) &session.getAttribute(key) &session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作

ShiroHandler

@Controller @RequestMapping("/shiro") public class ShiroHandler { @Autowired private ShiroService shiroService; @RequestMapping("/testShiroAnnotation") public String testShiroAnnotation(HttpSession session){ session.setAttribute("key", "value12345");//写入到Session shiroService.testMethod(); return "redirect:/list.jsp"; } ... }

ShiroService

public class ShiroService { @RequiresRoles({"admin"}) public void testMethod(){ System.out.println("testMethod, time: " + new Date()); //从Session中读取 Session session = SecurityUtils.getSubject().getSession(); Object val = session.getAttribute("key"); System.out.println("Service SessionVal: " + val); } ... } 24. SessionDao

SessionDao可让Session在数据库中CRUD。

SessionDao族谱

AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManagerMemorySessionDAO 直接在内存中进行会话维护EnterpriseCacheSessionDAO 提供了缓存功能的会话维护, 默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话。

配置示例

create table sessions ( id varchar(200), session varchar(2000), constraint pk_ sessions primary key(id) ) charset=utf8 ENGINE= InnoDB;

Session Dao

@Autowired private JdbcTemplate jdbcTemplate = nu1l; @Override protected Serializable doCreate(Session session){ Serializable sessionId = generateSessionId(session); assignSessionId,(session, sessionId); String sql = "insert into sessions(id, session) values(?, ?)";| jdbcTemplate.update(sql,sessionId,serializableUtils. serialize (session); return session.getId(); } @Override protected Session doReadSession(Serializable sessionId){ String sql = "select session from sessions where id=?"; List sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId); if (sessionStrList.size() == 0) return null; return SerializableUtils.deserialize(sessionStrList.get(0)); } @Override protected void doUpdate (Session session) { if (session instanceof ValidatingSession && ! ((ValidatingSession)session).isValid()) { return; String sql = "update sessions set session=? where id=?"; jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId()); } @Override protected void doDelete (Session session) { String sql = "delete from sessions where id=?"; jdbcTemplate.update(sql, session.getId()); }

SerializableUtils

public static String serialize (Session session) { try ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectoutputStream oos = new objectOutputStream(bos); oos.writeobject(session); return Base64.encodeTostring(bos.toByteArray()); } catch(Exception e){ throw new RuntimeException("serialize session error", e) ; } } public static Session deserialize (String sessionStr) { try ByteArrayInputStream bis = new ByteArrayInputStream(); Base64.decode(sessionStr)); ObjectInputstream ois = new objectInputstream(bis); return (Session)ois.readobject(); } catch (Exception e) { throw new RuntimeException ("deserialize session error", e); } } 25. 缓存 CacheManagerAware 接口

Shiro 内部相应的组件( DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware 并自动注入相应的CacheManager

Realm 缓存

Shiro 提供了 CachingRealm,其实现了CacheManagerAware 接口,提供了缓存的一些基础实现;

AuthenticatingRealm 及 AuthorizingRealm 也分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓存。

Session 缓存 如 SecurityManager 实现了 SessionSecurityManager,其会判断 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把CacheManager 设置给它。SessionManager 也会判断相应的 SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库。 ...

ehcache.xml

...

具体怎么使用的,视频没说,还需查阅更多资料

26. 认证和记住我的区别

Shiro 提供了记住我( RememberMe)的功能,比如访问如淘宝等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问,基本流程如下:

首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并保存下来;关闭浏览器再重新打开,会发现浏览器还是记住你的;访问一般的网页服务器端还是知道你是谁,且能正常访问;但是比如我们访问淘宝时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。 27. 实现Remember me

UsernamePasswordToken类中setRememberMe()

ShiroHandler

@Controller @RequestMapping("/shiro") public class ShiroHandler { ... @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password){ Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // rememberme token.setRememberMe(true);//设置remeberMe try { System.out.println("1. " + token.hashCode()); ... } }

在applicationContext.xml中设置rememberMe的寿命

... ... ...


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有