目录html
首发日期:2019-06-03
java
在以往的权限管理中,咱们的权限管理一般是有如下几个步骤:
1.建立用户,分配权限。
2.用户登陆,权限拦截器拦截请求,识别当前用户登陆信息
3.从权限表中判断是否拥有权限
从以上步骤中能够提取到如下三个问题。
三个问题:git
1.如何让Shiro拦截请求。
在web开发中,Shiro会提供一个拦截器来对请求进行拦截。github2.Shiro如何判断发起请求用户的身份?
在web开发中,会借助session来判断,若是禁用了session,那么可能须要重写一些方法。web3.如何判断权限?
Shiro使用realm来判断权限。
下面的也将以这三个问题为中心来描述Shiro。
算法
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <!-- 这里有用到日志打印,因此引入 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency>
对于不一样的场景有不一样依赖包【能够参考这个http://shiro.apache.org/download.html#latestSource】
基础包是shiro-core
,这里仅做基础示例因此仅仅导入这个包。spring
【一些代码能够参考https://github.com/apache/shiro/tree/master/samples/quickstart】
1.src/main/resources/shiro.ini的代码:数据库
# ----------------------------------------------------------------------------- # users用来定义用户 [users] # 用户名 = 密码,角色1,角色2... admin = secret, admin guest = guest, guest aa = 123456, guest # ----------------------------------------------------------------------------- # roles用来定义角色 [roles] # 角色 = 权限 (* 表明全部权限) admin = * # 角色 = 权限 (* 表明全部权限) guest = see aa = see
2.src/main/com/demo/ShiroDemo的代码:apache
package com.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemo { private static final Logger log = LoggerFactory.getLogger(ShiroDemo.class); public static void main(String[] args) { //1.建立SecurityManagerFactory IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取SecurityManager,绑定到SecurityUtils中 SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3.获取一个用户识别信息 Subject currentUser = SecurityUtils.getSubject(); //4.判断是否已经身份验证 if (!currentUser.isAuthenticated()) { // 4.1把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest"); // 4.2设置rememberme token.setRememberMe(true); try { // 4.3登陆. currentUser.login(token); } catch (UnknownAccountException uae) { //用户不存在异常 log.info("****---->用户名不存在: " + token.getPrincipal()); return; } catch (IncorrectCredentialsException ice) {// 密码不匹配异常 log.info("****---->" + token.getPrincipal() + " 的密码错误!"); return; } catch (LockedAccountException lae) {// 用户被锁定 log.info("****---->用户 " + token.getPrincipal() + " 已被锁定"); } catch (AuthenticationException ae) { // 其余异常,认证异常的父类 log.info("****---->用户" + token.getPrincipal() + " 验证发生异常"); } } // 5.权限测试: //5.1判断用户是否有某个角色 if (currentUser.hasRole("guest")) { log.info("****---->用户拥有角色guest!"); } else { log.info("****---->用户没有拥有角色guest"); return; } //5.2判断用户是否执行某个操做的权限 if (currentUser.isPermitted("see")) { log.info("****----> 用户拥有执行此功能的权限"); } else { log.info("****---->用户没有拥有执行此功能的权限"); } //6.退出 System.out.println("****---->" + currentUser.isAuthenticated()); currentUser.logout(); System.out.println("****---->" + currentUser.isAuthenticated()); } }
解析一下上面的代码作了什么:编程
1.在[users]
标签下以用户名 = 密码,角色1,角色2...
的格式建立了用户
2.在[roles]
标签下以角色 = 权限 (* 表明全部权限)
的格式为用户分配了角色
1.使用shiro.ini来获取了IniSecurityManagerFactory
2.经过IniSecurityManagerFactory获取SecurityManager,并绑定到SecurityUtils中
3.使用SecurityUtils获取一个用户识别信息Subject
4.对Subject对象判断是否已经身份验证(Authenticated)
5.将用户名和密码封装成UsernamePasswordToken对象,调用Subject对象的login方法来进行登陆
6.登陆成功后,调用Subject对象的hasRole方法来判断用户是否拥有某个角色
7.调用Subject对象的isPermitted方法来判断用户是否拥有某个行为
8.调用Subject对象的logout方法来退出。
上面展现了一个”登陆-权限验证-退出“的流程。但上面的一些代码仍是硬编码的,好比说上面的用户名和密码仍是从ini表中获取的,好比说上面的权限信息仍是从ini中获取的,这些问题都是须要解决的,下面会进行解决。
先来了解一些概念。
【下图是对这些概念的补充】
Cryptography :加密模块,提供对”密码“的加密等功能。
下面的例子是以继承了AuthenticatingRealm的自定义Realm来实现自定义认证。
认证依赖于方法doGetAuthenticationInfo,须要返回一个AuthenticationInfo,一般返回一个他的子类SimpleAuthenticationInfo,构造方法的第一个参数是用户名,第二个是验证密码,第三个是当前realm的className。
package com.demo.realms; import org.apache.shiro.authc.*; import org.apache.shiro.realm.AuthenticatingRealm; public class MyRealm extends AuthenticatingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { System.out.println("MyRealm认证中---->用户:"+token.getPrincipal()); // 能够从token中获取用户名来从数据库中查询数据 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假设这是从数据库中查询到的用户密码 // 建立一个SimpleAuthenticationInfo,第一个参数是用户名,第二个是验证密码,第三个是当前realm的className // 验证密码会与用户提交的密码进行比对 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
当建立了一个Realm以后,须要告诉SecurityManager,因此在shiro.ini中配置:
# ------------------------------------------------------------------- [main] myRealm = com.demo.realms.MyRealm # --------因为自定义认证,因此去除users,roles------------------------
这样子就能够进行自定义认证了,在上面的用户密码中都设置了"123456",因此若是输入的密码不正确都会认证失败。但上面没有设置受权,因此代码中要去掉受权的判断。
下面的例子是以继承了AuthorizingRealm的自定义Realm来实现自定义认证和自定义受权。
受权依赖于方法doGetAuthorizationInfo,须要返回一个AuthorizationInfo,一般返回一个他的子类SimpleAuthorizationInfo。构造SimpleAuthorizationInfo能够空构造,也能够传入一个Set<String> roles
来构造。
package com.demo.realms; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class RealmForDouble extends AuthorizingRealm { // 受权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1. 获取受权的用户 Object principal = principals.getPrimaryPrincipal(); System.out.println("RealmForDouble受权中---->用户:"+principal); //2.下面使用Set<String> roles来构造SimpleAuthorizationInfo SimpleAuthorizationInfo info = null; // SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<>(); if ("admin".equals(principal)){ roles.add("admin"); // 假设这个角色是从数据库中查出的 // 若是SimpleAuthorizationInfo实例化了, // 能够这样来加角色,行为须要这样添加 // 角色能够传构造函数来实例化SimpleAuthorizationInfo // info.addRole("admin"); // info.addStringPermission("*"); } if ("guest".equals(principal)){ roles.add("guest"); } info = new SimpleAuthorizationInfo(roles); return info; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble认证中---->用户:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假设这是从数据库中查询到的用户密码 // 建立一个SimpleAuthenticationInfo,第一个参数是用户名,第二个是验证密码,第三个是当前realm的className // 验证密码会与用户提交的密码进行比对 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
ini文件中也须要对应修改:
# ------------------------------------------------------------------- [main] myRealm = com.demo.realms.RealmForDouble # --------因为自定义认证,因此去除users,roles------------------------
这样子就能够进行自定义认证和受权了,上面的认证信息和受权信息都是能够修改为从数据库中获取的。
AuthorizingRealm:能够同时认证和受权。
AuthenticatingRealm:用于认证。
Realm:既能够用于认证也能够用于受权。
有好几个父类,怎么判断他们可否进行认证或受权呢?
当认证错误时会抛出异常,下面列举一下常见的异常。
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> </dependencies>
1.先配置Shiro拦截器:
<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.匹配SpringMVC的DispatcherServlet:
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
3.配置Spring的ContextLoaderListener:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
4.配置springmvc.xml【springmvc的配置文件】:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.progor"></context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler/> </beans>
5.在applicationContext.xml中配置Shiro:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--1. 配置 SecurityManager!--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--缓存管理器--> <property name="cacheManager" ref="cacheManager"/> <!--realms--> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> </bean> <!--2. 配置 CacheManager缓存管理器.--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!--缓存配置文件(这里暂不涉及,能够随便拷贝一个)--> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!--3. 配置 Realm--> <bean id="jdbcRealm" class="com.progor.realms.MyRealm"> </bean> <!--4. 配置 LifecycleBeanPostProcessor,用来管理shiro一些bean的生命周期--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!--5. 启用shiro 的注解。但必须在配置了 LifecycleBeanPostProcessor 以后才可使用--> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!--6. 配置 ShiroFilter.--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /login.jsp = anon /shiro/login = anon /shiro/logout = logout /** = authc </value> </property> </bean> </beans>
6.建立控制器来接受登陆请求,执行Shiro认证:
package com.progor.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/shiro") public class UserController { @RequestMapping("/login") public String login(String username,String password){ System.out.println(username+":"+password); Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); try { currentUser.login(token); } catch (AuthenticationException ae) { System.out.println("登陆失败: " + ae.getMessage()); } } return "redirect:/admin.jsp"; } @RequestMapping("/logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); System.out.println("退出成功"); return "redirect:/login.jsp"; } }
7.建立realm:
package com.progor.realms; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class MyRealm extends AuthorizingRealm { // 受权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Object principal = principals.getPrimaryPrincipal(); System.out.println("RealmForDouble受权中---->用户:"+principal); SimpleAuthorizationInfo info = null; Set<String> roles = new HashSet<>(); if ("admin".equals(principal)){ roles.add("admin"); } if ("guest".equals(principal)){ roles.add("guest"); } info = new SimpleAuthorizationInfo(roles); return info; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble认证中---->用户:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="123456";// 假设这是从数据库中查询到的用户密码 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName()); return info; } }
8.建立几个jsp,用于权限测试:
一个用于登陆的login.jsp,一个用于验证登陆成功的admin.jsp。【预期结果是若是未登陆,那么访问admin.jsp会重定向到login.jsp】
login.jsp:
<form action="shiro/login" method="POST"> username: <input type="text" name="username"/> <br><br> password: <input type="password" name="password"/> <br><br> <input type="submit" value="Submit"/> </form>
admin.jsp:
<body> <p>这是admin.jsp</p> <a href="shiro/logout">退出</a> </body>
9.配置ehcache.xml,能够参考https://github.com/apache/shiro/blob/master/samples/spring-mvc/src/main/resources/ehcache.xml
1.配置ShiroFilter是为了让ShiroFilter可以拦截请求来进行权限判断。
2.applicationContext中配置的Shiro请参考注释。
3.ehcache.xml是缓存管理器的配置文件。
上述的代码简略地演示了在Spring环境中Shiro的运行流程。下面将会对一些细节进行描述。
上面的ShiroFilter中有以下图的代码
这主要是用来定义ShiroFilter拦截哪些请求,以及怎么拦截请求的。
在上图中,左边是url,右边是拦截器。
常见的拦截器有:
/admin.jsp = roles[user]
/admin/deluser = prems["user:delete"]
shiro.apache.org/web.html#default-filters
/**
,这是表明全部请求,是为了拦截其他未定义拦截规则的请求。/login.jsp = anon
,因此就不会交给/**
来拦截了。/admin/** = authc, roles[administrator]
也是能够的。上面的ShiroFilter还配置了下图的属性,这是用来定义发生一些状况时跳转到哪一个页面的。
在上面都是使用硬编码的方式来定义拦截器链。下面将解决这个硬编码问题
一种方法是使用FilterChainResolver来处理,这里使用map的方式来处理。
定义一个类,核心方法是返回一个LinkedHashMap【有序是为了确保从上到下匹配】:
package com.progor.utils; import java.util.LinkedHashMap; public class FilterChainMap { // 使用静态工厂 public static LinkedHashMap<String, String> getFilterChainMap(){ LinkedHashMap<String, String> map = new LinkedHashMap<>(); // 下面的数据能够从数据库中查询出来。 map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/admin.jsp", "authc"); map.put("/**", "authc"); return map; } }
修改applicationContext.xml:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filteChainMap"></property> <!--去掉filterChainDefinitions--> </bean> <!--核心是获取这个map,因为使用了静态工厂,因此这样定义这个bean--> <bean id="filteChainMap" class="com.progor.utils.FilterChainMap" factory-method="getFilterChainMap" ></bean>
上面讲述了ShiroFilter的配置,解决了请求的拦截问题。
在上面的密码比对中,都是使用明文来比对。
而一般来讲,被存储起来的用户密码一般都是加密后的。也就是说,在使用SimpleAuthenticationInfo返回的认证信息时候,里面的密码信息是被加密过的,若是咱们直接拿用户提交的明文密码匹配的话就会匹配失败,因此咱们应该还须要告诉Shiro使用什么加密方式来进行密码比较。
在Shiro中,使用credentialsMatcher来解决这个问题。
在配置realm的时候,能够定义一个credentialsMatcher属性,例如:
<bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--定义加密的算法--> <property name="hashAlgorithmName" value="MD5"></property> </bean> </property> </bean>
密码加密一次后能够获得一串hash值,但还能够进行屡次加密来提升安全性。
<bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--定义加密的算法--> <property name="hashAlgorithmName" value="MD5"></property> <!--定义加密的次数--> <property name="hashIterations" value="1024"></property> </bean> </property> </bean>
除了多重加密,还能够加入一个”盐值“来进行加密。一我的的名字多是会重复的,但若是带上他的身份证的话,那么这我的就是特定惟一的。密码也是如此,直接将密码进行加密可能仍是比较容易分析出来的(网上有一些md5的密码库,常见的加密结果很容易查找出来),但若是加入一个具备比较罕见的参数来进行加密的话,那么获得的结果就会难以解析了。
盐值因为不是每个加密都是同样的,因此不能在realm中设置,须要在返回AuthenticationInfo时带上,这样securityManager就会对提交的明文密码依据加密算法、加密次数和盐值来进行加密后再与AuthenticationInfo中的密码进行比对。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("RealmForDouble认证中---->用户:"+token.getPrincipal()); UsernamePasswordToken upToken = (UsernamePasswordToken) token; String password="e10adc3949ba59abbe56e057f20f883e";// md5(123456) String salt = "lilei";//假设这个盐值是从数据库中查出的 ByteSource credentialsSalt = ByteSource.Util.bytes(salt); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,credentialsSalt,this.getName()); return info; }
上面已经讲述过realm的定义方法了,因此这里主要讲怎么让Shiro知道这多个realm。
只须要把新的realm配置成bean,并告诉securityManager便可。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="secondRealm"/> </list> </property> </bean> <!--省略其余配置 --> <bean id="jdbcRealm" class="com.progor.realms.MyRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <bean id="secondRealm" class="com.progor.realms.SecondRealm"></bean>
对于上面的多个realm的认证,你能够尝试两个地方使用不一样的密码来进行测试,借助sysout的话你会发现确实通过了两个realm.
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean> </property> </bean>使用上述的代码后,须要全部的realm都验证成功才能认证成功。
上面讲了权限拦截,下面讲一下怎么给请求/页面/业务来设置权限。
第一种是上面展现的使用拦截器链的方式,这种方式能够拦截一些请求/页面的非法权限操做。
编程式就是在代码中使用hasRole或isPermitted等方法来进行权限判断。
@RequestMapping("/deluser") public String deluser(){ Subject subject = SecurityUtils.getSubject(); if (subject.hasRole("admin")){ //一系列操做.... System.out.println("执行了删除用户的操做"); return "redirect:/admin.jsp"; }else{ System.out.println("你没有权限执行"); return "redirect:/unauthorized.jsp"; } }
除了hashRole,常见的方法还有:
hasRoles(List<String> roleIdentifiers)
:拥有List中的全部角色才返回truehasAllRoles(Collection<String> roleIdentifiers)
:拥有集合中的全部角色才返回trueisPermitted(String... permissions)
:是否拥有某个行为(支持传入多个参数)注解式就是使用注解来进行权限管理。【这些注解不能用在controller中】
public class UserService { @RequiresRoles("admin") // 须要角色admin public void deluser(){ System.out.println("执行了删除用户的操做"); } }
除了·@RequiresRoles("admin"),常见的注解还有:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
除此以外,还能够在jsp中进行受权,这将在后面再讲。
也能够在jsp中进行受权。
首先导入标签库:
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest></shiro:guest>
:当用户没进行认证时,显示标签中的内容。<shiro:user></shiro:user>
:当用户进行认证了,显示标签中的内容。<shiro:authenticated></shiro:authenticated>
:当用户已经认证时,显示标签中的内容。<shiro:notAuthenticated></shiro:notAuthenticated>
:当用户未认证的时候显示标签中的内容(包括“remember me”的时候)<shiro:principal />
:用来获取用户凭证(用户名等)(从AuthenticationInfo中获取),标签所在的位置将被替换成凭证信息<shiro:principal property="username" />
:若是存入的用户凭证是一个对象,那么可使用property指定获取这个对象中的属性。<shiro:hasRole name="角色"></shiro:hasRole>
:拥有指定角色,将显示标签中的内容。<shiro:hasAnyRoles name="角色1,角色2..."></shiro:hasAnyRoles>
:只要拥有多个角色中的一个就显示标签中的内容。<shiro:lacksRole name="角色"></shiro:lacksRole>
:没有某个角色将不显示标签中的内容<shiro:hasPermission name="行为"></shiro:hasPermission>
:若是拥有某个行为的权限,那么显示标签中的内容<shiro:lacksPermission name="行为"></shiro:lacksPermission>
:若是没有拥有某个行为,那么显示标签中内容<!-- 一个未登陆的场景 --> <shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest> <!-- 已登陆过,准备切换其余用户的场景 --> <shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user> <!-- 显示登陆用户的用户名的场景 --> Hello, <shiro:principal/>, how are you today? <!-- 用户已经认证经过的场景 --> <shiro:authenticated> <a href="/logout">退出</a>. </shiro:authenticated> <!-- 拥有某个角色的场景 --> <shiro:hasRole name="administrator"> <a href="createUser.jsp">建立用户</a> </shiro:hasRole> <!-- 拥有某个行为的场景 --> <shiro:hasPermission name="user:create"> <a href="createUser.jsp">建立用户</a> </shiro:hasPermission>
remember me 主要用于再次访问时仍然保留认证状态的场景。例如,离开某个网站后,两三天再打开仍然保留你的登陆信息。
remember me的功能的一个前提是在认证时使用了setRememberMe :
为true才会“记住我”。
记住个人权限并非authc
,而是user【用户已经身份验证/记住我】
因此作实验的要记得修改拦截器链。
maxAge:过时时间
httpOnly:禁止使用js脚本读取到cookie信息
【其余的不太经常使用,有兴趣的自查。还有domain之类的】
一种配置方法:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"/><!-- cookie的名称 --> <property name="httpOnly" value="true"/> <property name="maxAge" value="60"/><!-- 过时时间:60s --> </bean> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"/> </bean>
第二种配置方法:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <property name="rememberMeManager.cookie.maxAge" value="15"/> </bean>
这里仅仅只是“开了个门”,Shiro的世界还有不少广阔的地方。好比会话管理、单点登陆【这些何时有空再写吧】
若是你想了解更多,能够参考Shiro官方参考手册http://shiro.apache.org/reference.html
; 除此以外,张开涛的Shiro的PDF也是能够值得一看的。