Shiro 基本使用总结
来源:互联网 发布:淘宝千人千面 编辑:程序博客网 时间:2024/06/02 06:09
1.简介
Apache Shiro是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码学和会话管理。
认证、授权:
认证简单的说,就是登录的时候判断你的用户名和密码是否完全匹配,就是证明你是你。
授权,是在认证的基础之上,进行角色和权限的授予。权限决定了一个用户可以进行怎样的操作。
角色、权限:
权限定义了一个用户是否可以执行某个操作。
角色就是一组权限的集合。
我们通常是把一组权限绑定到一种角色上,再把一个或者多个角色赋给一个用户,这样就实现了权限的控制。即权限通过角色定义到用户上。角色作为权限的集合,方便了我们对权限的管理。
2.实现简单的登录功能
- 添加shiro依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope></dependency>
- 配置文件shiro.ini
[users]heqianqian = 12345666
3.编写测试代码
public class SimpleLoginTest { @Test public void test() throws Exception { ShiroUtils.login("classpath:shiro/shiro.ini", "heqianqian", "12345666"); }}
public class ShiroUtils { public static Subject getSubject(String path) { IniSecurityManagerFactory factory = new IniSecurityManagerFactory(path); SecurityManager manager = factory.getInstance(); SecurityUtils.setSecurityManager(manager); return SecurityUtils.getSubject(); } public static Subject login(String path, String userName, String passWord) { Subject subject = getSubject(path); UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord); try { subject.login(token); System.out.println("登录成功"); } catch (UnknownAccountException e) { System.out.println("无效的用户名"); e.printStackTrace(); } catch (IncorrectCredentialsException e) { System.out.println("密码错误"); e.printStackTrace(); } catch (AuthenticationException e) { e.printStackTrace(); } //subject.logout(); //System.out.println("退出成功"); return subject; }}
- Subject 就是我们登录的主体,这里相当于一个桥梁,我们所有的操作其实都要通过 Subject 来帮助我们完成。
- SecurityManager 类似于 spring MVC 中的 DispatcherServlet ,起到类似前端控制器的作用
- Realm 是安全数据源,我们要把类似于用户名和密码的信息存放到 Realm 中,Shiro 就可以帮助我们完成认证和授权等一系列操作
在这个例子中的 Realm 叫 IniRealm
3.使用JDBCRealm
前面例子的数据写的配置文件里,我们也可以从数据库读取数据
- 添加依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version></dependency><dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version></dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version></dependency>
- 编写配置文件jdbcRealm.ini
[main]# 表示实例化右边字符串表示的类,赋给左边字符串表示的变量。##JdbcRealm默认使用users表jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealmdataSource = com.mchange.v2.c3p0.ComboPooledDataSourcedataSource.driverClass = com.mysql.jdbc.DriverdataSource.user = rootdataSource.password = ****# 表示对 dataSource 这个变量设置属性值 jdbcUrl ,这个属性值是一个字符串。dataSource.jdbcUrl = jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8# 表示对 jdbcRealm 这个变量设置属性值 dataSource , 这个 dataSource 属性是上面实例化的一个对象,所以表示这个对象要使用前缀 `$`。jdbcRealm.dataSource = $dataSourcesecurityManager.realms = $jdbcRealm
- 数据库建表
DROP DATABASE db_shiro;# 创建数据库 db_shiroCREATE DATABASE db_shiro DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;# 使用数据库 db_shiroUSE db_shiro;drop table if exists users;create table users( id int(11) not null auto_increment comment '主键', username varchar(20) default null comment '用户名', password varchar(20) default null comment '密码', PRIMARY KEY (id))engine=innodb auto_increment=1 default charset=utf8 comment '用户表';insert into users(username,password) values('hqq','123456');
注意:
这里建表的表名是users 因为JdbcRealm默认查找users表里的数据
JdbcRealm 源码
public class JdbcRealm extends AuthorizingRealm { protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?"; protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?"; protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?"; protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";...}
4.编写测试方法
public class JdbcRealmTest { private String userName = "heqianqian"; private String passWord = "123"; private Logger logger = Logger.getLogger(JdbcRealmTest.class); @Test public void test() throws Exception { ShiroUtils.login("classpath:shiro/jdbcRealm.ini", userName, passWord); }}
4.自定义Realm
方式一:implements Realm
这种方式实现的 Realm 仅只能实现认证操作,并不能实现授权操作。
public class MapRealm implements Realm { private static Map<String, String> users = new HashMap<>(); static { users.put("heqianqian", "123"); users.put("lucy", "456"); } /** * 返回唯一的Realm名字 */ @Override public String getName() { System.out.println("设置Realm的名字"); return "My MapRealm"; } /** * 判断是否支持Token */ @Override public boolean supports(AuthenticationToken authenticationToken) { System.out.println("Map Realm 中给出支持的 Token 的方法"); // 表示仅支持 UsernamePasswordToken 类型的 Token return authenticationToken instanceof UsernamePasswordToken; } /** * 获取认证信息 */ @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("Map Realm 中返回认证信息的方法"); String userName = (String) authenticationToken.getPrincipal(); String passWord = new String((char[])authenticationToken.getCredentials()); if (!users.containsKey(userName)) { System.out.println("用户名错误!"); } else if (!users.get(userName).equals(passWord)) { System.out.println("密码错误!"); } return new SimpleAuthenticationInfo(userName, passWord, getName()); }}
编写配置文件mapRealm.ini
[main]# 声明了我们自己定义的一个 RealmmyMapRealm = heqianqian.shiro.realm.MapRealm# 将我们自己定义的 Realm 注入到 securityManager 的 realms 属性中去securityManager.realms = $myMapRealm
测试
public class MapRealmTest { @Test public void test() throws Exception { ShiroUtils.login("classpath:shiro/mapRealm.ini","heqianqian","123"); }}
方式二:extends AuthorizingRealm(比较常用的一种方式,因为这样做既可以实现认证操作,也可以实现授权操作)
public class MyStaticRealm extends AuthorizingRealm { /** * 用于授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 暂时忽略,以后介绍 return null; } /** * 用于认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("Static Realm 中认证的方法"); String userName = authenticationToken.getPrincipal().toString(); String password = new String((char[]) authenticationToken.getCredentials()); if (!"heqianqian".equals(userName)) { throw new UnknownAccountException("无效的用户名"); } else if (!"123".equals(password)) { throw new IncorrectCredentialsException("密码无效"); } return new SimpleAuthenticationInfo("heqianqian", "123", getName()); }}
编写配置文件mystaticRealm.ini
[main]myStaticRealm = heqianqian.shiro.realm.MyStaticRealmsecurityManager.realms = $myStaticRealm
测试
@Test public void testPermission() throws Exception { Subject subject = ShiroUtils.login("classpath:shiro/mystaticRealm.ini", "heqianqian", "123"); }
知识点:配置认证策略
这时候,我们会有一个疑问,securityManager 的属性既然是 realms,说明可以设置若干个 Realm,它们认证的顺序是如何的呢。
那么对于若干个 Realm,Shiro 提供了一种配置方式,让我们来决定在多个 Reaml 同时声明的情况下,采用哪些 Realm 返回的认证信息的方式,这就是我们的认证策略。
认证策略主要有以下三种:
1、FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
2、AtLeastOneSuccessfulStrategy: (这是默认使用的认证策略,即在不配置情况下 Shiro 所采用的认证策略)只要有一个 Realm 验证成功即可, 和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份验证成功的认证信息;
3、AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
配置示例
# 配置认证策略allSuccessfulStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategysecurityManager.authenticator.authenticationStrategy = $allSuccessfulStrategy
5. 基于字符串的角色和权限
编写配置文件
[users]hqq = 123,role1,role2lucy = 111,role1[roles]role1 = user:selectrole2 = user:add,user:update,user:delete
测试角色和权限
public class RoleTest { @Test public void testHasRole() throws Exception { Subject subject = ShiroUtils.login("classpath:shiro/shiro-role.ini", "hqq", "123"); assertEquals(true, subject.hasRole("role1")); assertEquals(true, subject.hasRole("role2")); assertEquals(true, subject.hasAllRoles(Arrays.asList("role1", "role2"))); subject.logout(); } @Test public void testCheckRole() throws Exception { Subject subject = ShiroUtils.login("classpath:shiro/shiro-role.ini", "hqq", "123"); subject.checkRole("role1"); subject.checkRole("role2"); subject.checkRoles(Arrays.asList("role1", "role2")); assertEquals(true, subject.hasRole("role2")); }}
public class PermissionTest { @Test public void testHasPermission() throws Exception { Subject subject = ShiroUtils.login("classpath:shiro/shiro-permission.ini", "hqq", "123"); assertEquals(true, subject.isPermitted("user:select")); subject.isPermitted("user:select", "user:add", "user:delete", "user:update"); assertEquals(true, subject.isPermittedAll("user:select", "user:select", "user:add", "user:delete", "user:update")); subject.logout(); } @Test public void testCheckPermission() throws Exception { Subject subject = ShiroUtils.login("classpath:shiro/shiro-permission.ini", "hqq", "123"); subject.checkPermission("user:select"); subject.checkPermissions("user:select", "user:add", "user:delete", "user:update"); subject.logout(); }}
6.自定义权限解析器和角色解析器
自定义权限解析规的步骤
步骤1:实现 Permission 接口
public class MyPermission implements Permission { private String resourceId; private String operator; private String instanceId; public MyPermission() { } public MyPermission(String permissionStr) { String[] strs = permissionStr.split("\\+"); if (strs.length > 1) { this.resourceId = strs[1]; } if (this.resourceId == null || "".equals(this.resourceId)) { this.resourceId = "*"; } if (strs.length > 2) { this.operator = strs[2]; } if (strs.length > 3) { this.instanceId = strs[3]; } if (this.instanceId == null || "".equals(this.instanceId)) { this.instanceId = "*"; } System.out.println("实例化 MyPermission 时 => " + this.toString()); } /** * 【这是一个非常重要的方法】 * 由程序员自己编写授权是否匹配的逻辑, * 我们这里的实现,是将 Realm 中给出的 Permission 和 ini 配置中指定的 PermissionResoler 中指定的 Permission 进行比对 * 比对的规则完全由我们自己定义 */ @Override public boolean implies(Permission permission) { if (!(permission instanceof MyPermission)) { return false; } MyPermission mp = (MyPermission) permission; if (!"*".equals(mp.resourceId) && !this.resourceId.equals(mp.resourceId)) { return false; } if (!"*".equals(mp.operator) && !this.operator.equals(mp.operator)) { return false; } if (!"*".equals(mp.instanceId) && !this.instanceId.equals(mp.instanceId)) { return false; } return true; } @Override public String toString() { return "MyPermission{" + "resourceId='" + resourceId + '\'' + ", operator='" + operator + '\'' + ", instanceId='" + instanceId + '\'' + '}'; }}
步骤2:实现 PermissionResolver 接口
实现 PermissionResolver 接口的意义在于告诉 Shiro 根据字符串的表现形式(表现特征),采用什么样的 Permission 进行匹配。
public class MyPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String s) { /*如果我们的权限字符串是以 “+” 号开头的话,就使用我们自定义的 MyPermission ,否则就使用默认的 WildcardPermission 解析这个字符串。*/ if (s.startsWith("+")) { return new MyPermission(s); } return new WildcardPermission(s); }}
说明:这段代码实现的一个效果是:如果我们的权限字符串是以 “+” 号开头的话,就使用我们自定义的 MyPermission ,否则就使用默认的 WildcardPermission 解析这个字符串。
步骤3:实现授权的逻辑
public class MyStaticRealm extends AuthorizingRealm { /** * 用于授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("Static Realm 中授权的方法"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRole("r1"); info.addRole("r2"); info.addRole("role1"); info.addStringPermission("+user+"); info.addObjectPermission(new MyPermission("+user+add+1")); return info; } /** * 用于认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("Static Realm 中认证的方法"); String userName = authenticationToken.getPrincipal().toString(); String password = new String((char[]) authenticationToken.getCredentials()); if (!"heqianqian".equals(userName)) { throw new UnknownAccountException("无效的用户名"); } else if (!"123".equals(password)) { throw new IncorrectCredentialsException("密码无效"); } return new SimpleAuthenticationInfo("heqianqian", "123", getName()); }}
编写配置文件
#配置自定义权限解析器permissionResolver = heqianqian.shiro.permission.MyPermissionResolverauthorizer.permissionResolver = permissionResolversecurityManager.authorizer = $authorizer
自定义角色匹配器
步骤1:实现 RolePermissionResolver 接口
public class MyRolePermissionResolver implements RolePermissionResolver { @Override public Collection<Permission> resolvePermissionsInRole(String s) { System.out.println("MyRolePermissionResolver"); if (s.contains("role1")) { return Arrays.asList((Permission) new MyPermission("+user+add+2")); } return null; }}
步骤2:在 shiro.ini 中配置我们的 RolePermissionResolver
authorizer = org.apache.shiro.authz.ModularRealmAuthorizer##配置RolePermissionResolverrolePermissionResolver = heqianqian.shiro.role.MyRolePermissionResolverauthorizer.rolePermissionResolver = rolePermissionResolver#配置自定义权限解析器permissionResolver = heqianqian.shiro.permission.MyPermissionResolverauthorizer.permissionResolver = permissionResolversecurityManager.authorizer = $authorizer
步骤3:在自定义的 Realm 的授权方法中添加角色
public class MyStaticRealm extends AuthorizingRealm { /** * 用于授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("static Realm 中授权的方法"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRole("role1"); return info; } /** * 用于认证 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 省略 }}
步骤 4 :判断用户是否具有授权方法中指定角色的权限
currentSubject.checkPermission("+user+add+1");
7. 加密
PasswordService service = new DefaultPasswordService();String str1 = service.encryptPassword("123456");String str2 = service.encryptPassword("123456");System.out.println(str1);System.out.println(str2);// 盐值是存放在加密以后的密码中的boolean boolean1 = service.passwordsMatch("123456",str1);System.out.println(boolean1);boolean boolean2 = service.passwordsMatch("123456",str2);System.out.println(boolean2)
自定义 Realm 的认证实现部分:
public class MyPasswordRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 验证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // default String userName = (String) authenticationToken.getPrincipal(); //String passWordFromDB = "$shiro1$SHA-256$500000$onR5YN4/BqH6toWLn9atsg==$g31FX9UERzy36yBlruzIZqGwCLf8V9P5p3hBrZQOAY0="; //md5+salt String passWordFromDB = "c39d11270c99cb00a13a6ac1e54effa2"; String salt = "hqq"; SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userName, passWordFromDB, getName()); info.setCredentialsSalt(ByteSource.Util.bytes(salt.getBytes())); return info; }}
配置文件
# 声明一个 Shiro 已经有的密码匹配的类passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher# 声明自定义的 Realm 类;myPasswordRealm= heqianqian.shiro.realm.MyPasswordRealm# 将 passwordMatcher 注入到自定义的 Realm 类中myPasswordRealm.credentialsMatcher=$passwordMatcher# 将自定义的 Realm 注入到 securityManager 中securityManager.realms=$myPasswordRealm
加盐
String originPassword = "123456";String salt = "hello";String md5 = new Md5Hash(originPassword,salt).toString();System.out.println(md5);
public class MyPasswordRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String username = authenticationToken.getPrincipal().toString(); // String password = new String((char[]) authenticationToken.getCredentials()); // 此时我们应该从数据库中根据 username 查询出对应的密码 String passwordFromDB = "eeb9bad681184779aa6570e402d6ef6c"; String salt = "hello"; SimpleAuthenticationInfo info= new SimpleAuthenticationInfo(username,passwordFromDB,getName()); // 设置加密算法的盐值,使用 ByteSource 这个工具类 info.setCredentialsSalt(ByteSource.Util.bytes(salt.getBytes())); return info; }}
配置文件
# 声明一个 Shiro 已经有的密码匹配的类hashMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcherhashMatcher.hashAlgorithmName=md5hashMatcher.hashSalted=hello# 声明自定义的 Realm 类myPasswordRealm=com.liwei.realm.MyPasswordRealm# 将 passwordMatcher 注入到自定义的 Realm 类中myPasswordRealm.credentialsMatcher=$hashMatcher# 将自定义的 Realm 注入到 securityManager 中securityManager.realms=$myPasswordRealm
参考文章
http://blog.csdn.net/lw_power?viewmode=contents
- Shiro 基本使用总结
- shiro使用总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- Shiro 权限框架使用总结
- shiro使用总结-简单实现
- shiro使用总结-自定义Realm
- [Shrio使用总结]-详解shiro配置文件
- 使用shiro实现权限控制学习总结
- shiro使用总结-项目集成开发
- shiro总结
- Makefile基本使用总结
- Git基本使用总结
- ProgressDialog 基本使用总结
- adb使用基本总结
- EF6基本使用总结
- Apache基本使用总结
- Oracle 恢复删除的表
- 5月25日,每日20行。
- 我的理解之JAVA中的4种访问权限
- ImageView 加载图片 , 原始比例 缩放 显示
- llinux 文件搜索命令
- Shiro 基本使用总结
- 算法谜题76 高效的车
- Android Intent机制
- Oracle数据库索引
- 解决<input type=“file” multiple> 多文件上传问题(可以多次选取文件,也可以一次选取多个文件),而且点击的是一个同一个div
- java编程思想读书笔记二:一切都是对象
- 使用C#编程解决数独求解(从图片识别到数独求解)
- java从入门到弃坑十七><
- 蓝桥杯单片机 基础篇1——————数码管的显示