一站式shiro

一站式shiro

本文章是根据http://jinnianshilongnian.iteye.com/blog/2018398进行缩减提取。

同时可以https://www.sojson.com/shiro;

为了不浪费大家时间,强烈建议不要看下面的内容,直接点击上面的两个网址进入看。


Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。

 

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

其他模块不再继续介绍,如想了解请看源网http://jinnianshilongnian.iteye.com/blog/2018936

 

 

(外部)大的角度来看,Shiro有三个主要的概念:Subject,SecurityManagerRealm

 

可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

 

也就是说对于我们而言,最简单的一个Shiro应用:

1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

 

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

其他模块省略。

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。

shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份最常见的principals和credentials组合就是用户名/密码了。

 Shiro的配置文件格式: 文件名.ini 。

外部编程环境身份验证的步骤:通过代码注释说明

@Test  

public void testHelloworld() {  

    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager  

    Factory<org.apache.shiro.mgt.SecurityManager> factory =  

            new IniSecurityManagerFactory("classpath:shiro.ini");    //2、得到SecurityManager实例 并绑定给SecurityUtils这是一个全局设置,设置一次即可;

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();  

    SecurityUtils.setSecurityManager(securityManager);  

    //3、通过SecurityManager得到Subject(其会自动绑定到当前线程)获取身份验证的token用户名/密码身份验证Token(即用户身份/凭证)  

    Subject subject = SecurityUtils.getSubject();  

    UsernamePasswordToken token = new UsernamePasswordToken("zhang""123");   

    try {  

      //4、登录,即身份验证其会自动委托给SecurityManager.login方法进行登录 

       subject.login(token);  

    } catch (AuthenticationException e) {  

        //5、身份验证失败捕获AuthenticationException或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等

    }    

    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录    

    //6、退出  其会自动委托给SecurityManager.logout方法退出。

    subject.logout();  

}  

  

内部shiro身份认证流程:

1、首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;

2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;SecurityManager接口继承了Authenticator另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,

3、Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;

4、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;

5、Authenticator会把相应的token传入Realm,。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException实现。

我的理解:就是从数据库获取用户的所有权限,和访问某方法或类等需要的权限(输入权限),相比较,确定是否能访问。

 

认证策略有多种,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy(默认):只要有一个Realm验证成功即可,返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

 

 

配置一个Realm   自定义Realm实现org.apache.shiro.realm.Realm接口(有3个方法要实现)。然后在.ini文件中指定次Realm实现,

#声明一个realm  

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1  

#指定securityManager的realms实现  

securityManager.realms=$myRealm1  

具体实现类:

public class MyRealm1 implements Realm {  

   @Override  //返回一个唯一的Realm名字  

    public String getName() {  

        return "myrealm1";  

    }  

    @Override  /判断此Realm是否支持此Token  

    public boolean supports(AuthenticationToken token) {  

        //仅支持UsernamePasswordToken类型的Token  

        return token instanceof UsernamePasswordToken;   

    }  

    @Override  /判断此Realm是否支持此Token  

    public AuthenticationInfo getAuthenticationInfo(Authenticat onToken token) throws AuthenticationException {  

     String username = (String)token.getPrincipal();//得到用户名

//得到密码  

    String password = new String((char[])token.getCredentials());        if(!"zhang".equals(username)) {  

            throw new UnknownAccountException();//如果用户名错误  

        }  

        if(!"123".equals(password)) {  

        throw new IncorrectCredentialsException();//如果密码错误  

        }  

        //如果身份认证验证成功,返回一个AuthenticationInfo实现;  

        return new SimpleAuthenticationInfo(username, password, getName());  

    }  

}

 


Realm配置   ini配置文件中

#声明一个realm  

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1  

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2  

#指定securityManager的realms实现  

securityManager.realms=$myRealm1,$myRealm2   

securityManager会按照realms指定的顺序进行身份认证。如果删除securityManager.realms那么securityManager会按照realm声明的顺序进行使用(即无需设置realms属性,其会自动发现),当我们显示指定realm后,其他没有指定realm将被忽略,如“securityManager.realms=$myRealm1”,那么myRealm2不会被自动设置进去。

 

以后一般继承AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

可以自定义AuthenticationStrategy认证策略自定义实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可

  

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

用过Spring Security的朋友应该比较熟悉对URL进行全局的权限控制,即访问URL时进行权限匹配;如果没有权限直接跳到相应的错误页面。Shiro也支持类似的机制,不过需要稍微改造下来满足实际需求。不过在Shiro中,更多的是通过AOP进行分散的权限控制,即方法级别的;而通过URL进行权限控制是一种集中的权限控制。

通常企业开发中将资源和权限表合并为一张权限表,如下:

资源(资源名称、访问地址);权限(权限名称、资源id

合并为:权限(权限名称、资源名称、资源访问地址)

 

对资源类型的管理称为粗颗粒度权限管理,即只控制到菜单、按钮、方法,粗粒度的例子比如:用户具有用户管理的权限,具有导出订单明细的权限。对资源实例的控制称为细颗粒度权限管理,即控制到数据级别的权限,比如:用户只允许修改本部门的员工信息,用户只允许导出自己创建的订单明细。

 基于url拦截是企业中常用的权限管理方法,实现思路是:将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过虑,过虑器获取到用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问。

 

主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。

资源在应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)打印文档等等。。。

权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。

Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

 

隐式角色:即直接通过角色来验证用户有没有操作权限,如在应用中CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。基于角色的访问控制

显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。RBAC基于资源的访问控制

 

Shiro支持三种方式的授权:

编程式:通过写if/else授权代码块完成: 

Subject subject = SecurityUtils.getSubject();  

if(subject.hasRole(“admin”)) {  

    //有权限  

else {  

    //无权限  

}   

 

注解式:通过在执行的Java方法上放置相应的注解完成: 

@RequiresRoles("admin")  

public void hello() {  

    //有权限  

}   

没有权限将抛出相应的异常;

 

JSP/GSP标签:在JSP/GSP页面通过相应的标签完成: 

<shiro:hasRole name="admin">  

<!— 有权限 —>  

</shiro:hasRole>   


 授权流程

1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;

2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;

3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;

4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

 

ModularRealmAuthorizer进行多Realm匹配流程:

1、首先检查相应的Realm是否实现了实现了Authorizer;

2、如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;

3、如果有一个Realm匹配那么将返回true,否则返回false。

 

如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:

1.1、如果调用hasRole*,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可;

1.2、首先如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;

2、通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色,并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);

3、接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。

 

Shiro对权限字符串缺失部分的处理

“user:view”等价于“user:view:*”;而“organization”等价于“organization:*”或者“organization:*:*”。可以这么理解,这种方式实现了前缀匹配。

另外如“user:*”可以匹配如“user:delete”、“user:delete”可以匹配如“user:delete:1”、“user:*:1”可以匹配如“user:view:1”、“user”可以匹配“user:view”或“user:view:1”等。即*可以匹配所有,不加*可以进行前缀匹配;但是如“*:view”不能匹配“system:user:view”,需要使用“*:*:view”,即后缀匹配必须指定前缀(多个冒号就需要多个*来匹配)。

 

字符串通配符权限:

规则:“资源标识符:操作:对象实例ID”  即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串,“:”表示资源/操作/实例的分割;“,”表示操作的分割;“*”表示任意资源/操作/实例。

subject().checkPermissions("system:user:update");  

用户拥有资源“system:user”的“update”权限。

subject().checkPermissions("system:user:update""system:user:delete");  

用户拥有资源“system:user”的“update”和“delete”权限。

role71=user:view:1  

对资源user的1实例拥有view权限。

#角色role2对资源user拥有createdelete权限 role2=user:create,user.delete

 

 

 

 

 

基于资源(也叫权限的访问控制(显示角色)这种方式的一般规则是“资源标识符:操作”

“用户名=密码,角色1,角色2”“角色=权限1,权限2”,

[users]  

zhang=123,role1,role2  

wang=123,role1  

[roles]  

role1=user:create,user:update  

role2=user:create,user:delete   

 

在身份认证完后,进入权限认证方法,

PrincipalCollection principals是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;然后根据用户名调用UserService接口获取角色及权限信息。

  String username = (String)principals.getPrimaryPrincipal(); 

 

 

[urls]部分的配置,其格式是: “url=拦截器[参数],拦截器[参数]”;即如果当前请求的url匹配[urls]部分的某个url模式,将会执行其配置的拦截器。比如anon拦截器表示匿名访问(即不需要登录即可访问);authc拦截器表示需要身份认证通过后才能访问;roles[admin]拦截器表示需要有admin角色授权才能访问;而perms["user:create"]拦截器表示需要有“user:create”权限才能访问。

url模式使用Ant风格模式

Ant路径通配符支持?、*、**,注意通配符匹配不包括目录分隔符“/”:

?:匹配一个字符,如”/admin?”将匹配/admin1,但不匹配/admin或/admin2;

*:匹配零个或多个字符串,如/admin*将匹配/admin、/admin123,但不匹配/admin/1;

**:匹配路径中的零个或多个路径,如/admin/**将匹配/admin/a或/admin/a/b。 

url模式匹配顺序

url模式匹配顺序是按照在配置中的声明顺序匹配,即从头开始使用第一个匹配的url模式对应的拦截器链

 

访问这些地址时会首先判断用户有没有登录,如果没有登录默会跳转到登录页面,默认是/login.jsp,可以通过在[main]部分通过如下配置修改: authc.loginUrl=/login

 

ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理.

 

shiro @RequiresPermissions和@RequiresRoles注解加在controller类级别无效,加在方法级别正常。注解在定义的时候是方法级别上的。

在类上注解不起作用,这个要重视,自己看看其他文章,如何解决的。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值