—— 代码存在问题,作为理解Shiro 运行流程教程
Shiro 使用
Shiro的jar包 :(Shiro已不推荐这样使用)
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-quartz</artifactId><version>1.2.3</version></dependency>
也可以通过引入shiro-all包括shiro所有的包:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-all</artifactId><version>1.2.3</version></dependency>
Shiro认证
认证流程图
![Shiro 基本使用 [有问题] - 图1](/uploads/projects/adrianjiao@uur5il/d02e101e722847b3a9eee297df9f264a.png)
入门程序:
1、创建普通的java 工程项目
2、导入jar 包
3、使用log4j.properties日志配置文件输出日志
log4j.rootLogger=debug, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
4、使用shiro.ini 文件 【通过Shiro.ini配置文件初始化SecurityManager环境】
配置 eclipse支持ini文件编辑:(IDEA 添加ini插件,创建ini文件即可)
![Shiro 基本使用 [有问题] - 图2](/uploads/projects/adrianjiao@uur5il/3f6ec37de5fbb96ba8f938d9b3855057.png)
在eclipse配置后,在classpath创建shiro.ini配置文件,为了方便测试将用户名和密码配置的shiro.ini配置文件中:
[users]zhang=123lisi=123
认证代码:
import org.apache.shiro.mgt.SecurityManager;// 用户登陆、用户退出@Testpublic void testLoginLogout() {// 注意:SecurityManager在java.lang包下也有同名的SecurityManager// 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");// 通过工厂创建SecurityManagerSecurityManager securityManager = factory.getInstance();// 将securityManager设置到运行环境中SecurityUtils.setSecurityManager(securityManager);// 创建一个Subject实例,该实例认证要使用上边创建的securityManager进行Subject subject = SecurityUtils.getSubject();// 创建token令牌,记录用户认证的身份和凭证即账号和密码UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");try {// 用户登陆subject.login(token);} catch (AuthenticationException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 用户认证状态Boolean isAuthenticated = subject.isAuthenticated();System.out.println("用户认证状态:" + isAuthenticated);// 用户退出subject.logout();isAuthenticated = subject.isAuthenticated();System.out.println("用户认证状态:" + isAuthenticated);}
认证执行流程
1、 创建token令牌,token中有用户提交的认证信息即账号和密码
2、 执行subject.login(token),最终由securityManager通过Authenticator进行认证
3、 Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带)
4、 IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。
以上程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm
shiro提供的realm 如下图:
![Shiro 基本使用 [有问题] - 图3](/uploads/projects/adrianjiao@uur5il/c550df2c3f39ae02153cf2c7d0b8cf47.png)
最基础的是Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权,通常自定义的realm继承AuthorizingRealm
自定义Realm
public class CustomRealm1 extends AuthorizingRealm {@Overridepublic String getName() {return "customRealm1";}//支持UsernamePasswordToken@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof UsernamePasswordToken;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//从token中 获取用户身份信息String username = (String) token.getPrincipal();//拿username从数据库中查询//....//如果查询不到则返回nullif(!username.equals("zhang")){//这里模拟查询不到return null;}//获取从数据库查询出来的用户密码String password = "123";//这里使用静态数据模拟。。//返回认证信息由父类AuthenticatingRealm进行认证SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());return simpleAuthenticationInfo;}//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// TODO Auto-generated method stubreturn null;}}
同时需要创建shiro-realm.ini 文件:
[main]#自定义 realmcustomRealm=cn.itcast.shiro.authentication.realm.CustomRealm1#将realm设置到securityManagersecurityManager.realms=$customRealm
测试代码同入门程序,将ini的地址修改为shiro-realm.ini
一般来说密码需要 加密 可以选择MD5加密方式这里就不详细说明。
Shiro授权
授权流程图
![Shiro 基本使用 [有问题] - 图4](/uploads/projects/adrianjiao@uur5il/988d6ad392aeb70e415c425c9aefee25.png)
授权方式
Shiro 支持三种方式的授权:1. 编程式:通过写if/else 授权代码块完成:Subject subject = SecurityUtils.getSubject();if(subject.hasRole(“admin”)) {//有权限} else {//无权限}2. 注解式:通过在执行的Java方法上放置相应的注解完成:@RequiresRoles("admin")public void hello() {//有权限}3. JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:<shiro:hasRole name="admin"><!— 有权限—></shiro:hasRole>
授权测试 :
1、创建存放权限的配置文件shiro-permission.ini,如下:
[users]#用户zhang的密码是123,此用户具有role1和role2两个角色zhang=123,role1,role2wang=123,role2[roles]#角色role1对资源user拥有create、update权限role1=user:create,user:update#角色role2对资源user拥有create、delete权限role2=user:create,user:delete#角色role3对资源user拥有create权限role3=user:create
注意:在ini文件中用户、角色、权限的配置规则是:“用户名=密码,角色1,角色2…” “角色=权限1,权限2…”,首先根据用户名找角色,再根据角色找权限,角色是权限集合
权限字符串规则
权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。例子:用户创建权限:user:create,或user:create:*用户修改实例001的权限:user:update:001用户实例001的所有权限:user:*:001
测试代码如下:
@Testpublic void testPermission() {// 从ini文件中创建SecurityManager工厂Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");// 创建SecurityManagerSecurityManager securityManager = factory.getInstance();// 将securityManager设置到运行环境SecurityUtils.setSecurityManager(securityManager);// 创建主体对象Subject subject = SecurityUtils.getSubject();// 对主体对象进行认证// 用户登陆// 设置用户认证的身份(principals)和凭证(credentials)UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");try {subject.login(token);} catch (AuthenticationException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 用户认证状态Boolean isAuthenticated = subject.isAuthenticated();System.out.println("用户认证状态:" + isAuthenticated);// 用户授权检测 基于角色授权// 是否有某一个角色System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));// 是否有多个角色System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));// subject.checkRole("role1");// subject.checkRoles(Arrays.asList("role1", "role2"));// 授权检测,失败则抛出异常// subject.checkRole("role22");// 基于资源授权System.out.println("是否拥有某一个权限:" + subject.isPermitted("user:delete"));System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user:create:1", "user:delete"));//检查权限subject.checkPermission("sys:user:delete");subject.checkPermissions("user:create:1","user:delete");}
注意:测试代码同认证代码,注意ini地址改为shiro-permission.ini,主要学习下边授权的方法;在用户认证通过后执行下边的授权代码
基于角色的授权
// 用户授权检测 基于角色授权// 是否有某一个角色System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));// 是否有多个角色System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
//对应的check方法:
subject.checkRole(“role1”);
subject.checkRoles(Arrays.asList(“role1”, “role2”));
上边check方法如果授权失败则抛出异常:
org.apache.shiro.authz.UnauthorizedException: Subject does not have role […..]
基于资源授权
// 基于资源授权System.out.println("是否拥有某一个权限:" + subject.isPermitted("user:delete"));System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user:create:1", "user:delete"));对应的check方法:subject.checkPermission("sys:user:delete");subject.checkPermissions("user:create:1","user:delete");
与上边认证自定义realm一样,大部分情况是要从数据库获取权限数据,这里直接实现基于资源的授权
授权自定义 realm 代码 如下:
直接在认证写的自定义realm类中完善doGetAuthorizationInfo方法,此方法需要完成:根据用户身份信息从数据库查询权限字符串,由shiro进行授权:
// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息String username = (String) principals.getPrimaryPrincipal();// 根据身份信息从数据库中查询权限数据//....这里使用静态数据模拟List<String> permissions = new ArrayList<String>();permissions.add("user:create");permissions.add("user.delete");//将权限信息封闭为AuthorizationInfoSimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();for(String permission:permissions){simpleAuthorizationInfo.addStringPermission(permission);}return simpleAuthorizationInfo;}
授权执行流程
1、 执行subject.isPermitted(“user:create”)
2、 securityManager通过ModularRealmAuthorizer进行授权
3、 ModularRealmAuthorizer调用realm获取权限信息
4、 ModularRealmAuthorizer再通过permissionResolver解析权限字符串,校验是否匹配
Shiro与spring web项目的整合
1、web.xml添加shiro Filter
<!-- shiro过虑器,DelegatingFilterProx会从spring容器中找shiroFilter --><filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
2、applicationContext-shiro.xml
<!-- Shiro 的Web过滤器 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><!-- 如果没有认证将要跳转的登陆地址,http可访问的url,如果不在表单认证过虑器FormAuthenticationFilter中指定此地址就为身份认证地址 --><property name="loginUrl" value="/login.action" /><!-- 没有权限跳转的地址 --><property name="unauthorizedUrl" value="/refuse.jsp" /><!-- shiro拦截器配置 --><property name="filters"><map><entry key="authc" value-ref="formAuthenticationFilter" /></map></property><property name="filterChainDefinitions"><value><!-- 必须通过身份认证方可访问,身份认 证的url必须和过虑器中指定的loginUrl一致 -->/loginsubmit.action = authc<!-- 退出拦截,请求logout.action执行退出操作 -->/logout.action = logout<!-- 无权访问页面 -->/refuse.jsp = anon<!-- roles[XX]表示有XX角色才可访问 -->/item/list.action = roles[item],authc/js/** anon/images/** anon/styles/** anon<!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->/** = user<!-- /**放在最下边,如果一个url有多个过虑器则多个过虑器中间用逗号分隔,如:/** = user,roles[admin] --></value></property></bean><!-- 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="userRealm" /></bean><!-- 自定义 realm --><bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1"></bean><!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 --><bean id="formAuthenticationFilter"class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"><!-- 表单中账号的input名称 --><property name="usernameParam" value="usercode" /><!-- 表单中密码的input名称 --><property name="passwordParam" value="password" /><!-- <property name="rememberMeParam" value="rememberMe"/> --><!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 --><property name="loginUrl" value="/loginsubmit.action" /></bean>
注意:
securityManager:这个属性是必须的。loginUrl:没有登录认证的用户请求将跳转到此地址,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。unauthorizedUrl:没有权限默认跳转的页面。
3、使用shiro注解授权
在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:
<!-- 开启aop,对类代理 --><aop:config proxy-target-class="true"></aop:config><!-- 开启shiro注解支持 --><beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager" /></bean>
修改Controller代码,在方法上添加授权注解,如下:
// 查询商品列表@RequestMapping("/queryItem")@RequiresPermissions("item:query")public ModelAndView queryItem() throws Exception { 。。。。。。。 }
上边代码@RequiresPermissions(“item:query”)表示必须拥有“item:query”权限方可执行
4、重新自定义realm
此realm先不从数据库查询权限数据,当前需要先将shiro整合完成,在上定义的realm基础上修改:
public class CustomRealm1 extends AuthorizingRealm {@Autowiredprivate SysService sysService;@Overridepublic String getName() {return "customRealm";}// 支持什么类型的token@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof UsernamePasswordToken;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 从token中 获取用户身份信息String username = (String) token.getPrincipal();// 拿username从数据库中查询// ....// 如果查询不到则返回nullif (!username.equals("zhang")) {// 这里模拟查询不到return null;}// 获取从数据库查询出来的用户密码String password = "123";// 这里使用静态数据模拟。。// 根据用户id从数据库取出菜单//...先用静态数据List<SysPermission> menus = new ArrayList<SysPermission>();;SysPermission sysPermission_1 = new SysPermission();sysPermission_1.setName("商品管理");sysPermission_1.setUrl("/item/queryItem.action");SysPermission sysPermission_2 = new SysPermission();sysPermission_2.setName("用户管理");sysPermission_2.setUrl("/user/query.action");menus.add(sysPermission_1);menus.add(sysPermission_2);// 构建用户身体份信息ActiveUser activeUser = new ActiveUser();activeUser.setUserid(username);activeUser.setUsername(username);activeUser.setUsercode(username);activeUser.setMenus(menus);// 返回认证信息由父类AuthenticatingRealm进行认证SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password, getName());return simpleAuthenticationInfo;}// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();//用户idString userid = activeUser.getUserid();// 根据用户id从数据库中查询权限数据// ....这里使用静态数据模拟List<String> permissions = new ArrayList<String>();permissions.add("item:query");permissions.add("item:update");// 将权限信息封闭为AuthorizationInfoSimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();for (String permission : permissions) {simpleAuthorizationInfo.addStringPermission(permission);}return simpleAuthorizationInfo;}}
5、登录controller
//用户登陆页面@RequestMapping("/login")public String login()throws Exception{return "login";}// 用户登陆提交@RequestMapping("/loginsubmit")public String loginsubmit(Model model, HttpServletRequest request)throws Exception {// shiro在认证过程中出现错误后将异常类路径通过request返回String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");if (UnknownAccountException.class.getName().equals(exceptionClassName)) {throw new CustomException("账号不存在");} else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {throw new CustomException("用户名/密码错误");} else{throw new Exception();//最终在异常处理器生成未知错误}}
6、退出
由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可
<!-- 退出拦截,请求logout.action执行退出操作 -->/logout.action = logout
注意:如需添加MD5校验需要修改applicationContext-shiro.xml:
<!-- 凭证匹配器 --><bean id="credentialsMatcher"class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><property name="hashAlgorithmName" value="md5" /><property name="hashIterations" value="1" /></bean><!-- 自定义 realm --><bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1"><property name="credentialsMatcher" ref="credentialsMatcher" /></bean>
7、表设计 (自己查的)
Shiro缓存配置
在applicationContext-shiro.xml中配置缓存管理器:
<!-- 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="userRealm" /><property name="sessionManager" ref="sessionManager" /><property name="cacheManager" ref="cacheManager"/></bean><!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"></bean>
Session管理
在applicationContext-shiro.xml中配置sessionManager
<!-- 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="userRealm" /><property name="sessionManager" ref="sessionManager" /></bean><!-- 会话管理器 --><bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"><!-- session的失效时长,单位毫秒 --><property name="globalSessionTimeout" value="600000"/><!-- 删除失效的session --><property name="deleteInvalidSessions" value="true"/></bean>
配置记住我(下次免登陆)
1、用户身份实现java.io.Serializable接口【即用户实体类】
2、修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置:
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> 改为 如下:
<bean id=*"formAuthenticationFilter"*class=*"cn.itcast.ssm.shiro.MyFormAuthenticationFilter"*><!-- 表单中账号的input名称 --><property name=*"usernameParam"* value=*"usercode"* /><!-- 表单中密码的input名称 --><property name=*"passwordParam"* value=*"password"* /><property name=*"rememberMeParam"* value=*"rememberMe"*/><!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 --><property name=*"loginUrl"* value=*"/loginsubmit.action"* /></bean>
3、修改applicationContext-shiro.xml
<!-- 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="userRealm" /><property name="sessionManager" ref="sessionManager" /><property name="cacheManager" ref="cacheManager"/><!-- 记住我 --><property name="rememberMeManager" ref="rememberMeManager"/></bean><!-- rememberMeManager管理器 --><bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"><property name="cookie" ref="rememberMeCookie" /></bean><!-- 记住我cookie --><bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"><constructor-arg value="rememberMe" /><!-- 记住我cookie生效时间30天 --><property name="maxAge" value="2592000" /></bean>
登录页面 添加如下:
<input type="checkbox" name="rememberMe" /> 自动登陆
附录
shiro过虑器过滤器简称 对应的java类anon org.apache.shiro.web.filter.authc.AnonymousFilterauthc org.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterperms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterport org.apache.shiro.web.filter.authz.PortFilterrest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilterroles org.apache.shiro.web.filter.authz.RolesAuthorizationFilterssl org.apache.shiro.web.filter.authz.SslFilteruser org.apache.shiro.web.filter.authc.UserFilterlogout org.apache.shiro.web.filter.authc.LogoutFilteranon:例子/admins/**=anon 没有参数,表示可以匿名使用。authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为httpsuser:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查注:anon,authcBasic,auchc,user是认证过滤器,perms,roles,ssl,rest,port是授权过滤器shiro的jsp标签Jsp页面添加:<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>标签名称 标签条件(均是显示标签内容)<shiro:authenticated> 登录之后<shiro:notAuthenticated> 不在登录状态时<shiro:guest> 用户在没有RememberMe时<shiro:user> 用户在RememberMe时<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时<shiro:hasRole name="abc"> 拥有角色abc<shiro:lacksRole name="abc"> 没有角色abc<shiro:hasPermission name="abc"> 拥有权限资源abc<shiro:lacksPermission name="abc"> 没有abc权限资源<shiro:principal> 显示用户身份名称<shiro:principal property="username"/> 显示用户身份中的属性值

