1.什么是shiro?
Apache Shiro 是一个基于JAVA的强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。
Shiro比较简单,而且比较独立。可以在javese和j2ee中使用,并且可以在分布式集群环境下使用。
a) Shiro结构体系
· Authentication:认证。验证用户是否合法。也就是登录。
· Authorization:授权。授予谁具有访问某些资源的权限。
· Session Management:会话管理。用户登录后的用户信息通过Session Management来管理。不管是什么应用中。
· Cryptography:加密。通过使用加密算法保持数据安全同时易于使用。
· Web Support:Shiro可以方便的集成到Web应用程序中。
· Caching:缓存来确保安全操作快速而又高效。支持多种缓存架构。Redis和ehcache。
· Concurrency:并发特性来支持多线程应用程序。
· Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
· "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
· "Remember Me":在会话中记住用户的身份,所以他们只需要在强制时候登录。
b) Shiro架构
Subject(org.apache.shiro.subject.Subject)主体
当前与软件进行交互的实体(用户,第三方服务,cron job,等等)的安全特定“视图”。
SecurityManager(org.apache.shiro.mgt.SecurityManager)安全管理器
Shiro 架构的核心。协调其管理的组件以确保它们能够一起顺利的工作。它还管理每个应用程序用户的Shiro 的视图,因此它知道如何执行每个用户的安全操作。
Authenticator(org.apache.shiro.authc.Authenticator)认证器
负责验证用户身份。
Authorizer授权器
负责为合法的用户指定其权限,控制用户可以访问那些资源。
Realms域
用户通过shiro来完成相关的安全工作,shiro不会去维护数据信息。在shiro工作过程中,数据的查询和获取工作是通过Realm从不同的数据源来获取的。Realm可以获取数据库信息,文本信息等。在shiro中可以有一个或多个Realm。
2.Authentication:认证
验证用户是否合法。
需要提交用户的身份和凭证给Shiro,以判断它们是否和应用程序预期的相匹配。
· Principals(身份)是Subject的‘identifying attributes(标识属性)’。Principals(身份)可以是任何能够证明Subject 的东西,不过更应该是独一无二的——通常是用户名或电子邮件地址。
· Credentials(凭证)通常是只被Subject 知道的秘密值,它用来作为一种起支持作用的证据,此证据事实上包含着所谓的身份证明。一些常见credentials(凭证)的例子有密码,生物特征数据如指纹和视网膜扫描,以及X.509 证书。
principal/credential 配对最常见的例子是用户名和密码。
认证流程:
代码实现:
新建java项目
导入shiro相关jar包
编写shiro的数据文件—配置文件
编码测试
public class AuthenticationDemo {
public static void main(String[] args) {
// 1.创建SecurityManager工厂 安全管理器 读取相应的配置文件 也可以读取数据库
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.通过SecurityManager工厂获取SecurityManager的实例。
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager对象设置到运行环境中。
SecurityUtils.setSecurityManager(securityManager);
// 4.通过SecurityUtils获取主体Subject
Subject subject = SecurityUtils.getSubject();
// 5.输入用户身份和凭证。这里是用户输入的信息,shiro.ini相当于数据库中的信息。
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1112");
try {
// 6.进行用户身份认证 登录
subject.login(token);
// 7.通过subject判断用户是否通过验证
if (subject.isAuthenticated()) { // 如果用户认证通过
System.out.println("用户登录成功");
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("用户登录失败!!!!!!");
}
}
}
常见的异常信息和处理:
在认证过程中的父异常为: AuthenticationException该异常有几个子类,分别对应不同的异常情况。
账户失效异常 DisabledAccountException
尝试次数过多 ExcessiveAttemptsException
用户不存在 UnknownAccountException
凭证过期异常 ExpiredCredentialsException
凭证不正确 IncorrectCredentialsException
虽然shiro为每一种异常都提供了准确的异常类,但在编写代码过程中,应提示给用户的 异常信息为模糊的。这样有助于安全。常见的处理方式:
try {
// 6.进行用户身份认证 登录
subject.login(token);
// 7.通过subject判断用户是否通过验证
if (subject.isAuthenticated()) { // 如果用户认证通过
System.out.println("用户登录成功");
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("用户名或密码不正确!!!!!!");
}
执行流程:
通过shiro相关api,创建SecurityManager并获取subject实例。
封装UsernamePasswordToken的Token信息。
通过关subject.login(Token)进行用户认证。
Subject接收token后通过实现类DelegatingSubject将token委托给SecurityManager完成认证。
SecurityManager是通过DefaultSecurityManager来完成相关功能。由DefaultSecurityManager的login()来完成登录认证。在login中调用authenticate()token。该方法是由AuthenticatingSecurityManager来完成的。在该类的authenticate()中,通过调用authenticator (认证器)来完成认证工作。authenticator默认是由其实现类ModularRealmAuthenticator来完成认证。 通过ModularRealmAuthenticator中的doAuthenticate()来获取Realms域信息。如果是单Realm,直接将token和Realm中的数据进行比较。如果是多Realm,需要通过AuthenticationStrategy认证策略来完成对应的认证工作。
通过subject.isAuthenticated()来判断是否认证成功。
3.JDBCRealm以及Authentication Strategy
使用shiro框架来完成认证工作,默认使用IniReam,如果需要使用其他Ream,需要进行相关设置。
[main] myRealm=com.hyr.realm #依赖注入 securityManager.realm=$myRealm |
[users] Zhangsan=1111 Lisi=222,role1,role2 |
[users] Zhangsan=1111,role1 [roles] Role1=user:add,user:delete |
Ini配置文件详解:
[main] section 是你配置应用程序的SecurityManager 实例及任何它的依赖组件(如Realms)的地方。
[users] section 允许你定义一组静态的用户帐户。这在大部分拥有少数用户帐户或用户帐户不需要在运行时被动态地创建的环境下是很有用的。
[roles] section 允许你把定义在[users] section 中的角色与权限关联起来。另外,这在大部分拥有少数用户帐户或用户帐户不需要在运行时被动态地创建的环境下是很有用的。
使用JdbcReam完成身份认证
通过观察JdbcRealm可知,要实现JdbcRealm:
需要为jdbcRealm设置DataSource
在指定的dataSource所对应的数据库有用户表users,该表中有username,password,password_salt等字段
实现步骤:
创建表并插入数据:
配置shiro.ini配置文件
[main] dataSource=com.mchange.v2.c3p0.ComboPooledDataSource dataSource.driverClass=com.mysql.jdbc.Driver dataSource.jdbcUrl=jdbc:mysql://localhost:3306/shiro dataSource.user=root dataSource.password=666666 jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource=$dataSource securityManager.realm=$jdbcRealm [users] zhangsan=111 lisi=222 |
编写代码测试
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111");
try {
subject.login(token);
if (subject.isAuthenticated()) {
System.out.println("登录成功");
}
} catch (AuthenticationException e) {
System.out.println("验证失败");
}
}
Authentication Strategy:认证策略
在shiro中有三种认证策略。
AtLeastOneSuccessfulStrategy 如果一个(或更多)Realm 验证成功,则整体的尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。
FirstSuccessfulStrategy 只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败。
AllSucessfulStrategy 为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一个验证成功,则整体尝试失败。
[main] #配置数据眼1 dataSource1=com.mchange.v2.c3p0.ComboPooledDataSource dataSource1.driverClass=com.mysql.jdbc.Driver dataSource1.jdbcUrl=jdbc:mysql://localhost:3306/shiro dataSource1.user=root dataSource1.password=666666 jdbcRealm1=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm1.dataSource=$dataSource1 #配置数据眼2 dataSource2=com.mchange.v2.c3p0.ComboPooledDataSource dataSource2.driverClass=com.mysql.jdbc.Driver dataSource2.jdbcUrl=jdbc:mysql://localhost:3306/shiro1 dataSource2.user=root dataSource2.password=666666 jdbcRealm2=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm2.dataSource=$dataSource2 #配置验证器 #全部成功 authenticationStrategy1=org.apache.shiro.authc.pam.AllSuccessfulStrategy #至少有一个成功 authenticationStrategy2=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy #只使用第一次成功的Realm,剩余的被忽略。全部失败则整体失败。 authenticationStrategy3=org.apache.shiro.authc.pam.FirstSuccessfulStrategy securityManager.realms=$jdbcRealm1,$jdbcRealm2 securityManager.authenticator.authenticationStrategy=$authenticationStrategy3 [users] zhangsan=1111 lisi=222 |
默认的策略是AtLeastOneSuccessfulStrategy,至少有一个成功。
设置认证策略:
4.自定义Realm实现身份认证
public class UserRealm extends AuthorizingRealm {
/**
* @category 授权的信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
/**
* @category 完成身份认证(从数据库中取数据),并返回认证信息。如果失败返回null
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户输入的用户名
String username = (String) token.getPrincipal(); // 获取身份信息
System.out.println("username====" + username);
// 根据用户名到数据库查询密码信息---模拟
// 假定才能够数据库获取的密码为1111
// TODO 这里查询数据库 获取用户信息
String pwd = "1111";
// 将从数据库中查询的信息封装到SimpleAuthenticationInfo中返回
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), pwd, getName());
return info;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return "userCustomRealm";
}
}
由于JDBCRealm固定了数据库表名和字段名,灵活性差。所以需要自定义Realm来实现用户身份认证。
Realm是一个接口,在接口中定义了根据token获得认证信息的方法。Shiro内部实现了一系列的realm,提供不同的功能。
AuthenticatingRealm提供了获取身份信息功能,AuthorizingRealm提供了获取权限信息的功能。通常自定义Realm需要继承AuthorizingRealm,这样既可以提供身份认证的自定义方法,也可以实现授权自定义方法。
注意:使用shiro完成权限管理,shiro并不会维护数据。Shiro中使用的数据需要程序员根据处理业务将数据传递个shiro接口。
5.散列算法(加密算法)
身份认证中会涉及加密,不加密数据不安全。Shiro实现了比较多的散列算法,如MD5,SHA等。并且提供了加盐功能。
// 使用MD5加密算法 加密
Md5Hash md5 = new Md5Hash("1111");
System.out.println("1111=" + md5.toString());
// 加盐
md5 = new Md5Hash("1111", "hyr");
System.out.println("1111=" + md5.toString());
// 迭代次数
md5 = new Md5Hash("1111", "hyr", 10);
System.out.println("1111=" + md5.toString());
在自定义ream中使用散列算法:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户输入的用户名
String username = (String) token.getPrincipal(); // 获取身份信息
System.out.println("username====" + username);
// 根据用户名到数据库查询密码信息---模拟
// 假定才能够数据库获取的密码为1111 从数据库获取密码和盐值
// TODO 这里查询数据库 获取用户信息
String pwd = "b63902e3ee5380787a2f112d279453a2"; //1111 hyr加盐 10次散列后的结果
String salt="hyr";
// 将从数据库中查询的信息封装到SimpleAuthenticationInfo中返回
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), pwd, ByteSource.Util.bytes(salt),getName());
return info;
}
Realm实现
配置文件:
[main] credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher # 哈希算法的名字 credentialsMatcher.hashAlgorithmName=MD5 # 散列迭代次数 credentialsMatcher.hashIterations=10 userRealm=cn.hyr.shiro.realm.UserRealm userRealm.credentialsMatcher=$credentialsMatcher securityManager.realm=$userRealm |
6.授权
给予身份认证通过的用户,访问某些资源的权限。
权限粒度:分为粗粒度和细粒度。
粗粒度:对user的CRUD。也就是说通常对表的操作。
细粒度:对记录的操作。如:只允许查看id为1的user的工资。Shiro通常管理的是粗粒度的权限,比如菜单,url,按钮。一般细粒度的权限通过业务来控制。
角色:权限的集合。
权限表示规则:资源:操作:实例。可以用通配符表示:
如 user:add表示对user有添加的权限。user:*表示对user具有所有权限 user:delete:100,表示对user标识为100的记录有删除的权限
Shiro中的权限流程
编码实现:
新建java项目
编辑shiro.ini配置文件
[users] zhangsan=1111,role1 lisi=2222,role2 [roles] role1=user:add,user:update,user:delete role2=user:* |
编码测试
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
// 1. 认证
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("认证不通过");
}
// 基于角色的授权
// zhangsan=1111,role1 lisi=2222,role2
boolean hasRole1 = subject.hasRole("role1");
System.out.println(hasRole1);
System.out.println("==============================");
// 判断多个角色
boolean[] hasRoles = subject.hasRoles(Arrays.asList("role1", "role2"));
for (int i = 0; i < hasRoles.length; i++) {
System.out.println(hasRoles[i]);
}
// 可以通过checkRole来检测是否具有某个角色,如果该不具有该角色则跑出UnauthorizedException
try {
subject.checkRole("role2");
} catch (UnauthorizedException e) {
e.printStackTrace();
System.out.println("不具有此角色");
}
// 检测多个角色
subject.checkRoles("role1");
// 基于资源的授权
boolean permitted = subject.isPermitted("user:delete");
System.out.println("是否具有删除权限:"+permitted);
// 判断多个权限
boolean[] permitteds = subject.isPermitted("user:add","user:delete","user:update");
for (int i = 0; i < permitteds.length; i++) {
System.out.println(permitteds[i]);
}
// 通过checkPermission检测权限,如果没有抛出异常
subject.checkPermission("user:select");
}
Shiro权限三种检查方式
编程式
if(subject.hasRole(“管理员”)){ // 操作 } |
注解式 在执行指定方法时,会检测是否具有该权限
@RequiresRoles(“管理员”) public void list(){ //查询记录 } |
标签
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <body> <shiro:hasPermission name="user:update"> <a href="#">更新</a> </shiro:hasPermission> </body> </html> |
授权流程
获取subject主体
判断主体是否通过身份认证
通过后调用subject. isPermitted*()或hasRole*()方法来进行权限的判断。
Subject是由其实现类DelegatingSubject来调用方法的,该类将处理交给了SecurityManager。
SecurityManager由其实现类DefaultSecurityManager来进行处理,该类的isPermitted来处理,其本质是父类AuthorizingSecurityManager来处理。该类将处理交给了authorizer(授权器)。
Authorizer由其实现类ModularRealmAuthorizer来处理,该类可以调用对应的Realm来处理。该类中有PermissionResolver来对权限字符串进行解析。在对应的Realm中也有对应的PermissionResolver交给WildcardPermissionResolve,该类调用WildcardPermission进行字符串的解析。
返回处理结果
7.自定义Realm授权
自定义Ream需要继承AuthorizingReam:
[main] userRealm=cn.hyr.shiro.realm.UserRealm securityManager.realm=$userRealm |
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = principals.getPrimaryPrincipal().toString(); // 获取主身份信息用户名
System.out.println("授权======");
System.out.println("username======" + username);
// 根据用户名到数据库查询用户对应的权限信息------模拟
List<String> permissions = new ArrayList<String>();
permissions.add("user:add");
permissions.add("user:delete");
permissions.add("user:update");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (String permission : permissions) {
authorizationInfo.addStringPermission(permission);
}
return authorizationInfo;
}
System.out.println(subject.isPermittedAll("user:add", "user:delete", "user:update"));
shiro.ini:
自定义Realm:
测试:
8.shiro整合ssm
整合ssm并实现用户登录和菜单权限
将shiro整合到ssm中
添加shiro相关jar包 shiro-all-x.x.x.jar
在web.xml中添加shiro的过滤器
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 设置true由servlet容器控制filter的生命周期 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean --> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
在src下添加applicationContext-shiro.xml配置文件
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 自定义Realm --> <bean id="userRealm" class="cn.czg.realm.UserRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"></property> </bean> <!-- 安全管理器定义 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> </bean> <!-- web.xml中shiro的filter对应的bean --> <!-- Shiro 的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 --> <property name="loginUrl" value="/login.do" /> <!-- 认证成功统一跳转到index.do,建议不配置,shiro认证成功自动到上一个请求路径 就算访问登录动作前访问的页面 --> <property name="successUrl" value="/index.do" /> <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 --> <property name="unauthorizedUrl" value="/refuse.do" /> <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --> <property name="filterChainDefinitions"> <value> <!-- /** = authc 所有url都必须认证通过才可以访问 /refuse.do=anon --> /toLogin.do=anon /login.do=authc /index.jsp=authc /index.do=authc /logout=logout <!-- /role/list.do=perms[role:list] --> /** = anon <!-- /** = anon所有url都可以匿名访问 --> </value> </property> </bean> <!-- 定义凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"></property> <property name="hashIterations" value="2"></property> </bean> <!-- 保证实现了shrio 内部 lifecycle函数的bean执行 由shrio管理其生命周期 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans> |
修改登录方法
// 验证已由shiro接管,如果登陆错误可接受shiroLoginFailure抛出的异常,将错误信息反馈到前台
@RequestMapping("/login.do")
public ModelAndView login(ModelMap map, HttpServletRequest req) {
// 获取失败的异常类的名称
String exceptionClassName = (String) req.getAttribute("shiroLoginFailure");
System.out.println("异常名称:"+exceptionClassName);
// 根据返回的异常类类名判断抛出是什么异常,从而得到验证错误信息
if (exceptionClassName != null) {
if (UnknownAccountException.class.getName().equals(exceptionClassName)) { // 未知(不存在)的用户
map.addAttribute("msg", "用户名不存在!!!");
} else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { // 凭证错误
map.addAttribute("msg", "用户名或密码错误!!!");
} else { // 其他错误
map.addAttribute("msg", "登录时发生异常,请重试!!!");
}
}
return new ModelAndView("login");
}
添加自定义Realm:UserRealm,实现身份认证
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;// 自动加载UserService
@Autowired
private PermissionService permissionService;// 加载授权方法类
@Autowired
private RoleService roleService;
@Autowired
private DepartmentService departmentService;
@Override
public String getName() {
return "userRealm";
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();// 获取用户名
// 根据用户名取出密码
User currentUser = null;
try {
currentUser = userService.findUserByName(username);// 通过用户名获取用户]
System.out.println(currentUser.toString());
} catch (UserException e) {
e.printStackTrace();
}
currentUser.setDept(departmentService.findDepartmentFromUserId(currentUser.getId()));
System.out.println(departmentService.findDepartmentFromUserId(currentUser.getId()).toString());
currentUser.setRole(roleService.findRoleFromUserId(currentUser.getId()));
// 将菜单设置到user中
// 出错点 :在取出菜单的时候 查询的是父类的pid 应该通过 用户id 查询出 权限的idzhaodao 对应的权限
System.out.println(roleService.findRoleFromUserId(currentUser.getId()).toString());
currentUser.setMenus(permissionService.findMenuByUserId(currentUser.getId()));
System.out.println(permissionService.findMenuByUserId(currentUser.getId()).size());
// 将权限内容设置到user中
// currentUser.setPermissions(permissionService.findPermissionByUserId(currentUser.getId()));
// 加盐 并返回用户信息
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currentUser, currentUser.getPassword(),
ByteSource.Util.bytes(currentUser.getSalt()), getName());
return info;
}
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User currentUser = (User) principals.getPrimaryPrincipal();// 获取当前用户
// 设置权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 授权信息
List<Permission> list = currentUser.getPermissions();// 获取当前用户的授权信息
for (Permission p : list) {
info.addStringPermission(p.getPercode());
}
return info;
}
}
凭证匹配器配置
<!-- 定义凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"></property> <property name="hashIterations" value="2"></property> </bean> |
UserRealm也要相应的改变:
// 加盐 并返回用户信息
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currentUser, currentUser.getPassword(),ByteSource.Util.bytes(currentUser.getSalt()), getName());
return info;
配置Logout过滤器
<!-- 配置Logout过滤器 --> <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="/toLogin.do"></property> </bean> |
配置authc过滤器----可以指定前台表单中input中name的值
<!-- 配置authc过滤器 可以指定前台input中接收name的值--> <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"></property> <property name="passwordParam" value="password"></property> </bean> |
配置授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取身份信息------该身份信息在认证时已经设置
User currentUser = (User) principals.getPrimaryPrincipal();// 获取当前用户
if (currentUser == null) {
return null;
}
// 设置权限信息
List<Permission> permissions = permissionService.findPermissionByUserId(currentUser.getId());
if(permissions==null || permissions.size()==0){
return null;
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 授权信息
for (Permission p : permissions) {
if(p.getPercode()!=null){
info.addStringPermission(p.getPercode());
System.out.println("用户具有的权限:"+p.getPercode());
}
}
return info;
}
@RequestMapping("/list.do")
@RequiresPermissions("role:list") // shiro会判断当前用户的的AuthorizationInfo
public String list(ModelMap map) {
map.addAttribute("list", roleService.list());
return "role/list";
}
添加权限验证注解
添加异常处理和页面显示权限信息
<!-- 判断抛出的异常, 如果是未授权异常,就跳到未授权页面 如果是未认证异常,跳转到登录页面 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop> <prop key="org.apache.shiro.authz.UnauthenticatedException">/login</prop> </props> </property> </bean> |
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> 。。。。。。 <shiro:hasPermission name="role:delete"> <a href="#">删除</a> </shiro:hasPermission> <shiro:hasPermission name="role:update"> <a href="#">修改</a> </shiro:hasPermission> |
在springmvc.xml中添加配置
在jsp页面进行权限判断
9.缓存
每次权限验证都会去数据库获取权限,可能查询一次权限相关的信息,会执行十几次方法去数据库查询数据。可以通过设置缓存来解决该问题,Shiro可以和ehcache或者redis集成。
添加ehcache.jar包。Shiro默认集成了一个ehcache的配置文件,也可以自己配置。
添加配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> </ehcache> |
在applicationContext-shiro.xml中添加cacheManager
<!-- 安全管理器定义 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> <property name="cacheManager" ref="cacheManager"></property> </bean>
<!-- 配置缓存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property> </bean> |
如果在运行中,主体的权限发生改变,应该从spring容器中调用realm中的缓存清理方法进行清理缓存。
// 清理缓存方法
public void clearCache() {
Subject subject = SecurityUtils.getSubject();
super.clearCache(subject.getPrincipals());
}
Session会话管理
<!-- 安全管理器定义 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> <property name="cacheManager" ref="cacheManager"></property> <property name="sessionManager" ref="sessionManager"></property> </bean>
<!-- 配置session会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 单位是毫秒 --> <property name="globalSessionTimeout" value="300000"></property> <!-- 删除无效session --> <property name="deleteInvalidSessions" value="true"></property> </bean> |
记住我 remember me
实体类必须实现序列化接口
设置登录页面时的”记住我”的表单域名
<!-- 配置authc过滤器 可以指定前台表单input中name的值 --> <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"></property> <property name="passwordParam" value="password"></property> <property name="rememberMeParam" value="rememberMe"></property> </bean> |
设置RememberManager 在applicationContext-shiro.xml中添加配置
<!-- 安全管理器定义 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="rememberMeManager" ref="rememberMeManager"></property> </bean>
<!-- 记住我配置 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"></property> </bean>
<!-- 记住我Cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- 设置cookie生存时间 --> <property name="maxAge" value="604800"></property> <!-- 设置cookie名称 --> <property name="name" value="rememberMe"></property> </bean> |
在过滤器链中配置哪些资源可以通过rememberMe再次访问
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --> <property name="filterChainDefinitions"> <value> <!-- /** = authc 所有url都必须认证通过才可以访问 /refuse.do=anon --> /toLogin.do=anon /login.do= authc /logout=logout /index.do = user <!-- /role/list.do=perms[role:list] --> /** = authc <!-- /** = anon所有url都可以匿名访问 --> </value> </property> |
页面配置
<div style="height: 20px;line-height: 20px;vertical-align: middle;"><input type="checkbox" name="rememberMe"><label>记住我</label></div> |