关于授权
授权模块一般包含两块:角色和资源
先简单看下配置文件
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=riversky
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
;\#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
;\#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
;授权
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
;\#指定securityManager的realms实现
securityManager.realms=$jdbcRealm
Shrio中SecurityManager中需要在配置文件中指定Authorizer来进行授权的处理。而Authorizer中至关重要的两个部分就是角色和资源,其中对于Authorizer的实现类主要是角色解析和资源解析。
角色解析
RolePermissionResolver 用于根据角色字符串来解析得到权限集合。使用时不是必须的实现的。
public class MyRolePermissionResolver implements RolePermissionResolver {
@Override
public Collection<Permission> resolvePermissionsInRole(String roleString) {
if("role1".equals(roleString)) {
return Arrays.asList((Permission)new WildcardPermission("menu:*"));
}
return null;
}
}
资源解析
何为资源?
一般在应用中用户所看到的和可以进行操作的各个 url对应的页面,模块,业务方法,数据的增删改查,和功能都可称之为资源。一般的表现形式大多靠get或者post返回的数据,以及get,post方式对应的url资源进行指定。
shiro的资源格式如下“资源:权限:实例”。
如user:delete:1表示对于user资源具有删除该资源下id为1的权限。
资源:可以进行任何方式的指定包含菜单、页面、按钮,以及业务数据的操作。
权限:view,delete,update,create
实例:id
并且为了达到资源的充分权限的方便配置。也可以用 * 来表示所有的意思。
SecurityManager的资源解析:
所有的权限最终都是与资源相结合的。因此当需要优化资源解析器的实时需要替代默认的WildcardPermissionResolver(PermissionResolver接口的实现类),进行自定义的权限解析。一般是基于比特位的认证方式,可根据自己的需求进行设计,一般用户量不是太大没必要进行该模块的设计,这里提供一个参考的实现
public class BitPermission implements Permission {
private String resourceIdentify;
private int permissionBit;
private String instanceId;
public BitPermission(String permissionString) {
String[] array = permissionString.split("\\+");
if(array.length > 1) {
resourceIdentify = array[1];
}
if(StringUtils.isEmpty(resourceIdentify)) {
resourceIdentify = "*";
}
if(array.length > 2) {
permissionBit = Integer.valueOf(array[2]);
}
if(array.length > 3) {
instanceId = array[3];
}
if(StringUtils.isEmpty(instanceId)) {
instanceId = "*";
}
}
@Override
public boolean implies(Permission p) {
if(!(p instanceof BitPermission)) {
return false;
}
BitPermission other = (BitPermission) p;
if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))) {
return false;
}
if(!(this.permissionBit ==0 || (this.permissionBit & other.permissionBit) != 0)) {
return false;
}
if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))) {
return false;
}
return true;
}
}
public class BitAndWildPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
if(permissionString.startsWith("+")) {
return new BitPermission(permissionString);
}
return new WildcardPermission(permissionString);
}
}
综合认证和授权–AuthorizingRealm 抽象类
通过继承该抽象类后实现其中的认证和授权模块,可以灵活的对Realm进行实现(摆脱固定的数据库形式,按照自己的解析方式进行认证和授权)
public class MyRealm extends AuthorizingRealm {
//认证模块
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username= (String) authenticationToken.getPrincipal();
String password=new String((char[])authenticationToken.getCredentials());
if(!"zhang".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if(!"123".equals(password)){
//认证错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username,password,getName())
}
//授权模块
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("role1");
authorizationInfo.addRole("role2");
authorizationInfo.addObjectPermission(new BitPermission("+user1+10"));
authorizationInfo.addObjectPermission(new WildcardPermission("user1:*"));
authorizationInfo.addStringPermission("+user2+10");
authorizationInfo.addStringPermission("user2:*");
return authorizationInfo;
}
}
常规的数据库方式使用–最简单的数据库使用方式
因为该方式按照官方的数据库字段和资源字段的定义,因此有局限性。当然使用上也是最简便的。
第一步maven依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.23</version>
</dependency>
第二步创建数据
数据库创建和资源的定义(mysql中存储)
drop database if exists shiro;
create database shiro;
use shiro;
create table users (
id bigint auto_increment,
username varchar(100),
password varchar(100),
password_salt varchar(100),
constraint pk_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_users_username on users(username);
create table user_roles(
id bigint auto_increment,
username varchar(100),
role_name varchar(100),
constraint pk_user_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_user_roles on user_roles(username, role_name);
create table roles_permissions(
id bigint auto_increment,
role_name varchar(100),
permission varchar(100),
constraint pk_roles_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_roles_permissions on roles_permissions(role_name, permission);
insert into users(username,password)values('zhang','123');
# --------------------------授权阶段,提供角色信息和其他的资源等-------------------------------
delete from users;
delete from user_roles;
delete from roles_permissions;
insert into users(username, password, password_salt) values('zhang', '123', null);
insert into user_roles(username, role_name) values('zhang', 'role1');
insert into user_roles(username, role_name) values('zhang', 'role2');
insert into roles_permissions(role_name, permission) values('role1', '+user1+10');
insert into roles_permissions(role_name, permission) values('role1', 'user1:*');
insert into roles_permissions(role_name, permission) values('role1', '+user2+10');
insert into roles_permissions(role_name, permission) values('role1', 'user2:*');
第三步:SecurityManager配置(注意jdbcRealm.permissionsLookupEnabled设置为true,不然进行资源的授权解析)
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
dataSource.password=riversky
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
;\#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
;\#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
;授权
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
;\#指定securityManager的realms实现
securityManager.realms=$jdbcRealm
第四步:测试
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/23.
*/
public class LoginController {
public void login(String configFile,String username,String pwd){
Factory<SecurityManager> factory=new IniSecurityManagerFactory(configFile);
SecurityManager manager=factory.getInstance();
SecurityUtils.setSecurityManager(manager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,pwd);
subject.login(token);
}
}
import cn.riversky.controller.LoginController;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/24.
*/
public class TestChar4 {
private LoginController loginController;
@Before
public void init(){
loginController=new LoginController();
}
@Test
public void shou(){
loginController.login("classpath:shiro-realm.ini","zhang","123");
Subject subject= SecurityUtils.getSubject();
System.out.println(subject.hasRole("role1"));
System.out.println(subject.isPermitted("user:create"));
// System.out.println(subject.isPermitted("user+2"));
//判断拥有权限:user:update and user:delete
System.out.println(subject.isPermittedAll("user:update", "user:delete"));
//判断没有权限:user:view
System.out.println(subject.isPermitted("user:view"));
}
}
授权扩展–自定义方式的测试
这里数据库数据就现在自己在授权和验证中模拟数据源(mysql或者其他配置文件的数据)
我们的目的是随心所遇的创建自己的角色解析方式(基于角色的授权)和资源解析方式(按照字节优化的方式)
这里需要关注的事件主要有-角色解析(通过自定义角色解析器然后注入到配置文件),资源解析(通过自定的资源解析其注入到配置文件)、Relm(通过继承AuthorizingRealm实现认证以及权限验证赋予过程),配置文件(组件SecurityManager),+测试文件
- 角色解析
package cn.riversky.controller;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.RolePermissionResolver;
import org.apache.shiro.authz.permission.WildcardPermission;
import java.util.Arrays;
import java.util.Collection;
/**
* 通过该方式可以实现粗粒度的基于角色的权限赋予
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/24.
*/
public class MyRolePermissionResolver implements RolePermissionResolver{
@Override
public Collection<Permission> resolvePermissionsInRole(String s) {
//这里其实应该从dao中进行角色查询,然后添加权限的。这里进行了写死的权限赋予
if("role1".equals(s)) {
return Arrays.asList((Permission)new WildcardPermission("menu:*"));
}
return null;
}
}
- 资源解析
package cn.riversky.controller;
import com.alibaba.druid.util.StringUtils;
import org.apache.shiro.authz.Permission;
/**
* 优化权限系统(可以根据需求进行分割符号的指定)
* 权限字符串格式:+ 资源字符串 + 权限位 + 实例 ID;以 + 开头中间通过 + 分割;权限:0 表示所有权限;1 新增(二进制:0001)、2 修改(二进制:0010)、4 删除(二进制:0100)、8 查看(二进制:1000);如 +user+10 表示对资源 user 拥有修改 / 查看权限。
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/24.
*/
public class BitPermission implements Permission {
private String resourceIdentify;
private int permissionBit;
private String instanceId;
public BitPermission(String permissionString) {
String[] array = permissionString.split("\\+");
if(array.length > 1) {
resourceIdentify = array[1];
}
if(StringUtils.isEmpty(resourceIdentify)) {
resourceIdentify = "*";
}
if(array.length > 2) {
permissionBit = Integer.valueOf(array[2]);
}
if(array.length > 3) {
instanceId = array[3];
}
if(StringUtils.isEmpty(instanceId)) {
instanceId = "*";
}
}
@Override
public boolean implies(Permission p) {
if(!(p instanceof BitPermission)) {
return false;
}
BitPermission other = (BitPermission) p;
if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))) {
return false;
}
if(!(this.permissionBit ==0 || (this.permissionBit & other.permissionBit) != 0)) {
return false;
}
if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))) {
return false;
}
return true;
}
}
package cn.riversky.controller;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.authz.permission.WildcardPermission;
/**
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/24.
*/
public class BitAndWildPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String s) {
if(s.startsWith("+")) {
return new BitPermission(s);
}
return new WildcardPermission(s);
}
}
- Relm
import com.alibaba.druid.util.StringUtils;
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.ArrayList;
import java.util.List;
/**
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/24.
*/
public class MyReamlm extends AuthorizingRealm {
/**
* 添加角色
* Role自己设计
* 思路:应该通过Dao查询到role,并且通过Role.getRoleName()进行角色查询
* @param username
* @param info
*/
private void addRole(String username, SimpleAuthorizationInfo info) {
// List<Role> roles = roleDao.findByUser(username);
List<String> roles = new ArrayList<String>();
roles.add("role1");
roles.add("role2");
if(roles!=null&&roles.size()>0){
for (String role : roles) {
info.addRole(role);
}
}
}
/**
* 添加权限
* 资源类Permission需要自己设计
* @param username
* @param info
* @return
*/
private SimpleAuthorizationInfo addPermission(String username,SimpleAuthorizationInfo info) {
// List<Permission> permissions = permissionDao.findPermissionByName(username);
List<String> permissions = new ArrayList<String>();
permissions.add("+user2+10");
permissions.add("user3:*");
//具有user1的修改和查看权限
permissions.add("+user1+10");
for (String permission : permissions) {
info.addStringPermission(permission);//添加权限
// info.addStringPermission(permission.getUrl());//添加权限
}
return info;
}
//认证模块
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username= (String) token.getPrincipal();
String password=new String((char[])token.getCredentials());
//用户名错误zhang--应该通过Service层进行校验
if(!"zhang".equals(username)){
throw new UnknownAccountException();
}
//认证错误--应该通过Service层进行校验
if(!"123".equals(password)){
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username,password,getName());
}
//授权模块
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username= (String) principals.fromRealm(getName()).iterator().next();
if(!StringUtils.isEmpty(username)){
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
addRole(username,authorizationInfo);
addPermission(username,authorizationInfo);
return authorizationInfo;
}
return null;
}
}
- 配置文件
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
;权限解析
permissionResolver= cn.riversky.controller.BitAndWildPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
;角色解析--基于角色的权限赋予
rolePermissionResolver= cn.riversky.controller.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
;\#指定securityManager的realms实现
realm= cn.riversky.controller.MyReamlm
securityManager.realms=$realm
- 测试文件
package cn.riversky.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
* @author riversky E-mail:riversky@126.com
* @version 创建时间 : 2018/1/23.
*/
public class LoginController {
public void login(String configFile,String username,String pwd){
Factory<SecurityManager> factory=new IniSecurityManagerFactory(configFile);
SecurityManager manager=factory.getInstance();
SecurityUtils.setSecurityManager(manager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,pwd);
subject.login(token);
}
}
@Test
public void demo(){
loginController.login("classpath:shirio-ream.ini","zhang","123");
Subject subject= SecurityUtils.getSubject();
System.out.println(subject.hasRole("role1"));
//基于角色的验证方式
System.out.println(subject.isPermitted("menu:create:1"));
//基于资源的验证方式
System.out.println(subject.isPermitted("user3:view"));
System.out.println(subject.isPermitted("+user2+10"));//新增和查看权限
}