简介
- Java安全框架
- Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
- 三个核心组件:Subject、SecurityManager和Realms。
- Subject:当前操作用户,代表了当前用户的安全操作。所有Subject都绑定到SEcurityManager,与Subject的所有交互都会委托给SecurityManager,可以把Subject认为是一个门面,SecurityManager才是实际的执行者。
- SecurityManager:安全管理器,Shiro框架核心,管理所有用户的安全操作。即所有与安全有关的操作都会与SecurityManager交互;它管理着所有Subject,负责与其他组件进行交互,相当于SpringMvc的DispatcherServlet前端控制器。
- Realm:Shiro从Realm(领域)获取安全数据(用户、角色、权限),就是说SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从REalm得到用户相应的权限进行验证;可以把Realm看成DataSource,即安全数据源。
Shiro和Spring Security比较
Shiro比Spring更容易使用,实现和最重要的理解
Spring Security更加知名的唯一原因是因为品牌名称
“Spring”以简单而闻名,但讽刺的是很多人发现安装Spring Security很难
然而,Spring Security却有更好的社区支持
Apache Shiro在Spring Security处理密码学方面有一个额外的模块
Spring-security 对spring 结合较好,如果项目用的springmvc ,使用起来很方便。但是如果项目中没有用到spring,那就不要考虑它了。
Shiro 功能强大、且 简单、灵活。是Apache下的项目,比较可靠,且不跟任何的框架或者容器绑定,可以独立运行
实战
依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency>
- 包含SecurityManager等最基础的对象。
shiro.ini:准备用户库
[users] zhang=123 wang=123
测试
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.apache.shiro.mgt.SecurityManager; import org.junit.Assert; import org.junit.Test; /** * @Author haien * @Description 运用shiro测试登录登出 * @Date 2019/2/15 **/ public class LoginLogoutTest { @Test public void testHelloWorld(){ //1、获取SecurityManager工厂,并使用ini配置文件初始化SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory= new IniSecurityManagerFactory("classpath:config/shiro.ini"); //2、得到SecurityManager实例,并绑定SecurityUtils SecurityManager securityManager=factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到Subject,创建用户名、密码身份验证Token(即用户身份凭证) Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken("zhang","123"); //可能不止用户名、密码,如登录时允许用户名、邮箱、手机选一登录 try { //4、登录,即身份验证 subject.login(token); }catch (AuthenticationException e){ //5、身份验证失败 } Assert.assertEquals(true,subject.isAuthenticated()); //断言用户是否已登录 //6、退出 subject.logout(); } }
- 其中,验证失败抛出AuthenticationException或其子类,如,DisabledAccountException(禁用的账号)、LockedAccountException(锁定的账号)、UnknownAccountException(错误的账号)、、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等。
- 对于页面错误消息的显示,最好使用用户名/密码错误,而不是用户名错误、密码错误,防止一些恶意用户没法扫描账号库。
身份验证流程
- 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- Authenticator会把相应的表单参数封装类AuthenticationToken传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了,如果返回一个身份信息封装类AuthenticationInfo则身份验证成功。
PS:此处可以配置多个Realm(上面的ini配置方式就是使用org.apache.shiro.realm.text.IniRealm),将按照相应的顺序及策略进行访问。
Realm:获取用户库
public interface Realm { //返回一个唯一的Realm名字 String getName(); //判断此Realm是否支持此Token boolean supports(AuthenticationToken var1); //根据Token获取认证信息 AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException; }
单Realm配置:自定义Realm实现类
public class MyRealm1 implements Realm { /** * @Author haien * @Description 获得唯一的Realm名字 * @Date 2019/2/15 * @Param [] * @return java.lang.String **/ @Override public String getName() { return "myrealm1"; } /** * @Author haien * @Description 判断此Realm是否支持此Token * @Date 2019/2/15 * @Param [authenticationToken] * @return boolean **/ @Override public boolean supports(AuthenticationToken token) { //仅支持UsernamePasswordToken return token instanceof UsernamePasswordToken; } /** * @Author haien * @Description 根据Token获得Token信息 * @Date 2019/2/15 * @Param [authenticationToken表单参数封装类] * @return org.apache.shiro.authc.AuthenticationInfo身份信息封装类 **/ @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //表单参数,来自login方法 String username=(String)authenticationToken.getPrincipal(); String password=new String((char[])authenticationToken.getCredentials()); //String password=(String)authenticationToken.getCredentials(); //若用户名错误 if(!"zhang".equals(username)){ //zhang来自用户库 throw new UnknownAccountException(); } //若密码错误 if(!"123".equals(password)){ //123来自用户库 throw new IncorrectCredentialsException(); } //身份验证成功,返回一个AuthenticationInfo实现 return new SimpleAuthenticationInfo(username,password,getName()); } }
配置ini文件指定自定义Realm实现
;第三方用户库:指定自定义Realm(可以在这个Realm里面定义用户库位置) myRealm1=com.haien.shiroHelloWorld.realm.MyRealm1 //相当于调用public无参构造创建对象 ;指定securityManage的realms实现(通过¥name来引入上面的Realm定义) securityManager.realms=$myRealm1 //相当于调用setter方法设置对象引用
多Realm配置。此处我们使用显式指示顺序的方式指定Realm的顺序,若删去第4行,SecurityManager则会按照Realm声明的顺序进行验证(即无需设置realms属性,会自动发现);而显式指定后,没有被指定到的Realm将会被忽略。
1 myRealm1=com.haien.shiroHelloWorld.realm.MyRealm1 2 myRealm2=com.haien.shiroHelloWorld.realm.MyRealm2 3 ;多Realm配置 4 securityManager.realms=$myRealm1,$myRealm2
Shiro默认提供的Realm
- 一般继承AuthorizingRealm即可,本身有权限验证doGetAuthorizationInfo(),又继承了用户验证doGetAuthenticationInfo,也继承了缓存功能。其中主要默认实现如下:
- org.apache.shiro.realm.text.IniRealm:[users]部分指定用户名/密码及其角色;[roles]部分指定角色即权限信息;
- org.apache.shiro.realm.text.PropertiesRealm: user.username=password,role1,role2指定用户名/密码及其角色;role.role1=permission1,permission2指定角色及权限信息;
- org.apache.shiro.realm.jdbc.JdbcRealm:通过sql查询相应的信息,如“select password from users where username = ?”获取用户密码,“select password, password_salt from users where username = ?”获取用户密码及盐;“select role_name from user_roles where username = ?”获取用户角色;“select permission from roles_permissions where role_name = ?”获取角色对应的权限信息;也可以调用相应的api进行自定义sql;
- 特别地,值需要用户验证而不需要授权,则继承AuthenticatingRealm即可。
- 而若实现Realm的话,一般是重写getAuthenticationInfo()实现用户验证,没有授权给功能。
JDBC Realm
- 先到数据库shiro下建表:users,user_roles,roles_permissions,并添加一个用户:username-zhang,password-123.
ini配置
;jdbc用户库 jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm //实例名=全限定类名,创建一个实例 dataSource=com.alibaba.druid.pool.DruidDataSource dataSource.driverClassName=com.mysql.jdbc.Driver //实例名.属性=值,自动调用setter进行赋值 dataSource.url=jdbc:mysql://127.0.0.1:3306/shiro dataSource.username=root dataSource.password=123456 jdbcRealm.dataSource=$dataSource securityManager.realms=$jdbcRealm
代码实例
- ideaProjects/shiroHelloWorld
参考文章
- 张开涛跟我学shiro