shiro安全框架

it2024-02-24  70

shiro安全框架

什么是shiro?

shiro是一个基于java的开源的安全管理框架;可以完成认证,授权,会话管理,加密,缓存等功能。

为什么学习shiro?

java中,安全管理框架有spring security和shiro;spring security要依赖于spring,并且比较复杂,学习曲线比较高;shiro比较简单,而且shiro比较独立,既可以在java se中使用,也可以在java ee使用,并且在分布式集群环境下也可以使用。

shiro结构体系

Authentication认证

验证用户是否合法,也就是登录

Authorization授权

授予谁具有访问某些资源的权限。

Session Management会话管理

用户登录后的用户信息通过Session Management来进行管理,不管是在什么应用中。

Cryptography加密

提供了常见的一些加密算法,使得在应用中可以很方便的实现数据安全,并且使用很便捷

Web Support web应用程序支持

shiro可以很方便的集成到web应用程序中。

Caching缓存

shiro提供了对缓存的支持,支持多种缓存架构,如:spring里面的ehcache,还支持缓存数据库如redis

Concurrency并发支持

支持多线程并发访问

Testing测试

Run As

支持一个用户在允许的前提下使用另外一个身份登录

Remember Me

记住我

Shiro的架构

Subject主体

可以是用户,也可以是第三方程序等;用于获取主体信息,principals和credentials

Security Manager安全管理器

安全管理器是shiro架构的核心,由其来协调管理shiro各个组件之间的工作。

Authenticator认证器

负责验证用户的身份

Authorizer授权器

负责为合法的用户指定其权限,控制用户可以访问哪些资源

Realm域

用户通过Shiro来完成相关的安全工作,Shiro是不会去维护数据信息的。在Shiro的工作过程中,数据的查询和获取工作是通过Realm从不同的数据源来获取的。Realm可以获取数据库信息,文本信息等,在Shiro中可以有一个Realm,也可以有多个;

Authentication用户认证

需要提交身份和凭证给Shiro

Principals:用户的身份信息,是Subject的标识属性。能够唯一标识Subject。如电话号码,电子邮箱,身份证号码

Credential:凭证,就是密码,是只被Subject知道的秘密值,也可以是数字证书等

Principals/Credentials最常见的组合就是用户名和密码,在Shiro中通常使用UsernamePasswordToken来指定身份和凭证信息。

在shiro中用户的认证流程

代码实现

新建java项目

导入shiro相关的jar包

commons-beanutils-1.9.2.jar commons-logging-1.2.jar junit-4.10.jar shiro-all-1.2.3.jar slf4j-api-1.7.7.jar log4j-1.2.17.jar slf4j-log4j12-1.7.5.jar 或者 <dependencies> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> </dependencies>

编写shiro的相关配置,创建SecurityManager工厂的时候使用

[users] xiaokaige=1234 xiaoshen=1234

编码测试

package com.xiaokaige; 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; import org.junit.Test; public class XiaokaigeAuthentication { public static void main(String[] args) { } @Test public void test01() { //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、假设登录的用户名和密码为zhangsan和1111,这里的用户名和密码表示登录时填入的信息, // shiro.ini文件中的信息相当于数据库中存放的用户信息 UsernamePasswordToken token = new UsernamePasswordToken("xiaokaige", "1234"); try { //6、进行用户身份认证 subject.login(token); System.out.println("用户登录成功!"); }catch (AuthenticationException e){ e.printStackTrace(); System.out.println("用户名或密码不正确"); } } }

5、日志文件

log4j.rootLogger=debug, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

shiro异常信息树

账户失效、尝试次数过多、用户不正确、凭证过期、凭证不正常等

虽然shiro为每一种异常都提供了准确的异常类,但是在编写代码过程中应提示给用户的异常信息为用户名或密码不正确

执行流程

1、通过Shiro的相关API,创建SecurityManager,获取Subject实例

2、封装token信息

3、通过subject.login(token)进行用户认证

Subject接收token后,通过其实现类DelegatingSubject将token委托给SecurityManager来完成认证

public void login(AuthenticationToken token) throws AuthenticationException { this.clearRunAsIdentitiesInternal(); //将token委托给SecurityManager来完成认证 Subject subject = this.securityManager.login(this, token); String host = null; PrincipalCollection principals; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject)subject; principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals != null && !principals.isEmpty()) { this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken)token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = this.decorate(session); } else { this.session = null; } } else { String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } } //实现认证器,授权器和Session管理器 public interface SecurityManager extends Authenticator, Authorizer, SessionManager { Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException; void logout(Subject var1); Subject createSubject(SubjectContext var1); }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wGVc3VsQ-1603246954855)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201019113440915.png)]

默认使用的是DefaultSecurityManager的login来完成认证相关的功能,调用了该方法中的authenticate方法,该方法在AuthenticatingSecurityManager类中;调用认证器authenticator方法;authenticator的默认实现类ModularRealmAuthenticator;通过ModularRealmAuthenticator中的doAuthenticate来获取Realms信息;如果是单realm,直接将token和realm中的数据进行比较,判断是否认证成功,如果是多realm,需要通过Authentication Strategy来完成对应的认证工作。

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { //认证信息,包括有身份信息和凭证信息 //认证 info = this.authenticate(token); } catch (AuthenticationException var7) { AuthenticationException ae = var7; try { this.onFailedLogin(token, ae, subject); } catch (Exception var6) { if (log.isInfoEnabled()) { log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6); } } throw var7; } Subject loggedIn = this.createSubject(token, info, subject); this.onSuccessfulLogin(token, info, loggedIn); return loggedIn; } public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { return this.authenticator.authenticate(token); } protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { this.assertRealmsConfigured(); Collection<Realm> realms = this.getRealms(); return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken); }

通过subject.isAuthenticated()来判断是否认证成功

JDBCRealm

1、使用shiro框架来完成认证工作,默认情况下使用的事iniRealm,如果需要使用其他的Realm,那么需要进行相关的配置

2、ini配置文件讲解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bigfv9z2-1603246954856)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201019134156550.png)]

[main]段

[main]section是你配置应用程序的SecurityManager实例及任何它的依赖组件(如Realm)的地方

[main] myRealm=com.xiaokage.MyRealm #类似于spring的依赖注入 secruityManager.realm=$myRealm

[users]段

[users] section允许你定义一组静态的用户帐户。这在大部分拥有少数用户帐户或用户帐户不需要在运行时被动态地创建的环境下是很有用的。可以配置账户,还可以配置用户的角色以下是一个例子:

[users] #可以配置账户,还可以配置账户的角色 zhangsan=111 lisi=2222,role1,role2

[roles]段

[roles] [roles] section允许你把定义在[users] section中的角色与权限关联起来。另外, 这在大部分拥有少数用户帐户或用户帐户不需要在运行时被动态地创建的环境下是很有用的。以下是一个例子:

[users] zhangsan=111,role1 [roles] role1=user:add,user:delete
使用内置的jdbcRealm来完成身份认证

通过观察JdbcRealm可知,要实现JdbcRealm:

需要为JdbcRealm设置dataSource

在指定的dataSource所对应的数据库中应有用户表user,该表中有username,password,password_salt等字段

实现步骤:

新建数据库表users

CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, `password_salt` varchar(20) DEFAULT NULL, `status` varchar(2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 insert into users values (null,'zhangsan','1111','x','1') insert into users values (null,'lisi','1111','x','1')

写配置文件

$表示引用对象

[main] dataSource=com.alibaba.druid.pool.DruidDataSource dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql://localhost:3306/xiaokaige_dev dataSource.username=root dataSource.password=root jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource=$dataSource securityManager.realm=$jdbcRealm

编写测试

@Test public void test02() { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "111"); try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("认证成功"); } }catch (AuthenticationException e){ System.out.println("验证失败"); } }

测试结果

通过查询users表来查找是都有该用户

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tqeji5Ni-1603246954857)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201019143506312.png)]

Authentication Strategy 认证策略

shiro中有3中认证策略

AtLeastOneSuccessfulStrategy

至少有一个成功

FirstSuccessfulStrategy

第一个成功

AllSuccessfulStrategy

所有的都成功

默认使用的是第一种策略

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noEFRZ8Y-1603246954858)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201019144621600.png)]

配置认证策略

[main] #配置数据源 dataSource=com.alibaba.druid.pool.DruidDataSource dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql://localhost:3306/xiaokaige_dev dataSource.username=root dataSource.password=root jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm #$表示引用对象 jdbcRealm.dataSource=$dataSource #配置认证器 authenticationStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy #再配置一个Realm dataSource1=com.alibaba.druid.pool.DruidDataSource dataSource1.driverClassName=com.mysql.jdbc.Driver dataSource1.url=jdbc:mysql://localhost:3306/xiaokaige_dev2 dataSource1.username=root dataSource1.password=root jdbcRealm1=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm1.dataSource=$dataSource1 securityManager.realms =$jdbcRealm,$jdbcRealm1 securityManager.authenticator.authenticationStrategy=$authenticationStrategy

AllSuccessfulStrategy,当配置的两个库中均有测试的用户的时候,会认证通过,否则就会认证失败

FirstSuccessfulStrategy, 第一个Realm验证成功,整体认证将被视为成功,且后续Realm将被忽略

AtLeastOneSuccessfulStrategy,只要有一个(或更多)的Realm验证成功,那么认证将被视为成功

自定义Realm实现身份认证

JdbcRealm已经实现了从数据库中获取用户的验证信息,但是jdbcRealm灵活性太差。如果要实现

自己的一些特殊应用时将不能支持,这个时候可以通过自定义Realm来实现身份的认证功能。

public interface Realm { String getName(); boolean supports(AuthenticationToken var1); AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException; }

Realm是一个接口,在接口中定义了根据token获得认证信息的方法。shiro内容实现了一系列的realm,这些不同Realm实现类提供了不同的功能。AuthenticatingRealm实现了获取身份信息的功能,而AuthorizingRealm实现了获取权限信息的功能,通常自定义Realm需要继承AuthorizingRealm,这样可以提供了身份认证的自定义方法也可以实现授权的自定义方法。

编写自定义的Realm

package com.xiaokaige.realm; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.sql.*; public class UserRealm extends AuthorizingRealm { //获取认证信息并完成认证 //如果认证失败返回null protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取身份信息 String username = (String) token.getPrincipal(); System.out.println("用户输入的用户名:" + username); //从数据库中获取到用户的密码 Connection conn = null; PreparedStatement prep = null; ResultSet rs = null; String password = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xiaokaige_dev","root","root"); String sql = "select password from users where username = ?"; prep = conn.prepareStatement(sql); prep.setString(1,username); rs = prep.executeQuery(); while(rs.next()){ password = rs.getString("password"); } } catch (Exception e) { e.printStackTrace(); }finally { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { prep.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } //将从数据库中查询的信息封装到simpleAuthenticationInfo中 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,getName()); return simpleAuthenticationInfo; } //获取权限信息 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } }

编写测试

@Test public void test02() { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro2.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) { e.printStackTrace(); System.out.println("验证失败"); } }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FPl1QaE-1603246954859)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201019155133328.png)]

注意:使用shiro来实现权限管理,shiro并不会维护数据,shiro中使用的数据,需要程序员处理业务将数据传递给shiro相应的接口

散列算法(加密算法)

在身份认证过程中往往会涉及加密,如果不加密那么数据信息不安全。Shiro内部实现了较多的散列算法。如MD5,SHA等。并且提供了加盐功能。

不加盐比加盐要安全很多

@Test public void test03(){ Md5Hash decodedStr = new Md5Hash("1111"); System.out.println("Md5加密" + decodedStr); Md5Hash decodedStr2 = new Md5Hash("1111", "xx"); System.out.println("Md5加盐加密" + decodedStr2); Md5Hash decodedStr3 = new Md5Hash("1111", "xx", 2); System.out.println("Md5加盐加密迭代2次" + decodedStr3); //SecurityManager就是利用的SimpleHash类加密,所以配置的时候需要将加密方式也配置上去 SimpleHash simpleHashStr = new SimpleHash("md5", "1111", "xx", 2); System.out.println("SimpleHash类加密" + simpleHashStr); }

在自定义Realm使用散列算法

在认证过程中从数据库中除了查出password之外,还需要查出盐

在自定义Realm中加入自定义凭证器

//获取认证信息并完成认证 //如果认证失败返回null protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取身份信息 String username = (String) token.getPrincipal(); System.out.println("用户输入的用户名:" + username); //从数据库中获取到用户的密码,盐; Connection conn = null; PreparedStatement prep = null; ResultSet rs = null; String password = null; String salt = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xiaokaige_dev","root","root"); String sql = "select password,salt from users where username = ?"; prep = conn.prepareStatement(sql); prep.setString(1,username); rs = prep.executeQuery(); while(rs.next()){ password = rs.getString("password"); salt = rs.getString("password_salt"); } } catch (Exception e) { e.printStackTrace(); }finally { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { prep.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } //将从数据库中查询的信息封装到simpleAuthenticationInfo中 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),getName()); return simpleAuthenticationInfo; }

编写配置

#使用的是Md5加盐加密,迭代次数2次 [main] credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 userRealm=com.xiaokaige.realm.UserRealm userRealm.credentialsMatcher=$credentialsMatcher securityManager.realm=$userRealm

编写测试

@Test public void test02() { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro2.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("lisi", "1111"); try { subject.login(token); if (subject.isAuthenticated()) { System.out.println("认证成功"); } } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("验证失败"); } }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z18VxSzt-1603246954860)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201020110505287.png)]

授权

1、授权:给身份认证通过的人授予他可以访问某些资源的权限

2、权限粒度:分为粗粒度和细粒度。

粗粒度:对user的crud。也就是说通常对表的操作。

细粒度:是对记录的操作。如:只允许查询id为1的user的工资。一般涉及到业务,而且量比较大

Shiro一般管理的是粗粒度的权限,比如:菜单,按钮,url;而细粒度的权限一般都是通过业务来控制

3、角色:角色就是权限的集合

4、权限表示规则:资源:操作:实例。可以用通配符表示:

如 user:add 表示对user有添加的权限

​ user:delete:100,表示对user标识为100的记录有删除的权限

5、Shiro中的权限流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySGRKJXZ-1603246954861)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201020111654423.png)]

6、编码实现:

编写配置

[users] zhangsan=1111,role1 lisi=1111,role2 [roles] role1=user:add,user:update,user:delete role2=user:*

编写测试代码

@Test public void test04() { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro3.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111"); try { //认证 subject.login(token); }catch (Exception e){ e.printStackTrace(); System.out.println("认证不通过"); } //判断该用户是否拥有该角色 boolean isHasRole1 = subject.hasRole("role1"); System.out.println(isHasRole1); boolean isHasRole2 = subject.hasRole("role2"); System.out.println(isHasRole2); //可以一次判断用户是否拥有多个权限 boolean[] isHasRole3 = subject.hasRoles(Arrays.asList("role1", "role2")); System.out.println(Arrays.toString(isHasRole3)); //可以判断用户是否拥有这个集合中所有的元素 boolean isHasAllRoles = subject.hasAllRoles(Arrays.asList("role1", "role2")); System.out.println(isHasAllRoles); //可以通过checkRole来检测是否具有某个角色,如果不具有该角色则抛出UnauthorizedException异常 //subject.checkRole("role2"); //可以通过checkRoles来检测是否具有多个角色,如果不具有集合中任意一个角色就抛出UnauthorizedException异常 //subject.checkRoles("role1","role2"); //基于资源的授权 boolean flagOne = subject.isPermitted("user:delete"); System.out.println(flagOne); //判断是否拥有传入的所有的权限 boolean flagTwo = subject.isPermittedAll("user:delete", "user:add"); System.out.println(flagTwo); //检验用户是否拥有哪个权限,没有则会抛出UnauthorizedException异常 //subject.checkPermission("user:xx"); }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jG6hIn6k-1603246954862)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201020114413970.png)]

7、Shiro中的权限检查方式有3中

1、编程式

上面那种就是,如

if(subject.hasRole("xxxRole")){ //操作某个资源 }

2、注解式

在执行指定的方法时会检测是否具有该权限

@RequiresRoles("xxxRole") public void list(){ //查询数据 }

3、标签

通过shiro的标签来控制

<shiro:hasPermission name="user:update"> <a href="#">更新</a> </shiro:hasPermission>

授权流程

1、subject主体 2、主体是否通过身份认证 3、调用subject.isPermited*/hasRole*来进行权限的判断 1、Subject是由其实现类DelegatinSubject来调用方法的,该类将处理交给SecurityManager 2、SecurityManager由其实现类DefaultSecurityManager来进行处理,该类的isPermitted来处理,本质是父类AuthorizingSecurityManager来处理的,该类将处理交给了authorizer(授权器) 3、Authorizer由其实现类ModularRealmAuthoriz来处理,该类可以调用对应的Realm来获取数据,在该类有PermissionResolver对权限字符串进行解析,在对应deRealm中也有对应的PermissionResolver,然后交给wildcardPermissionResolver,该类调用wildcardPermission来进行权限字符串的解析 4、返回处理结果

自定义Realm实现授权

仅仅通过配置文件来指定授权不够灵活,并且不方便。在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息,保存到数据库中。所以需要从数据库中获取相关的数据信息,可以使用Shiro提供的jdbcRealm来实现,也可以自定义realm来实现。使用jdbcRealm往往也不够灵活;所以在实际应用中大多数情况下都是自定义realm来实现。

2、自定义Realm需要继承AuthorizingRealm

授权代码

//获取权限信息 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { String username = principal.getPrimaryPrincipal().toString(); System.out.println("------授权"); System.out.println("username======" + username); //根据用户名到数据库中查询该用户对应的权限信息------模拟 List<String> permissions = new ArrayList<>(); permissions.add("user:add"); permissions.add("user:delete"); permissions.add("user:update"); permissions.add("user:find"); Object credentials; SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (String permission : permissions) { info.addStringPermissions(permissions); } return info; }

配置文件

[main] userRealm=com.xiaokaige.realm.UserRealm securityManager.realm=$userRealm [users] zhangsan=1111

编写测试

@Test public void test02() { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro4.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) { e.printStackTrace(); System.out.println("验证失败"); } System.out.println(subject.isPermittedAll("user:add", "user:delete")); }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rB6JE1vP-1603246954863)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201020140946508.png)]

SSM整合Shiro

添加Shiro相关jar包

<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>

在web.xml中添加shiro的配置

<!--配置shiroFilter通过代理来配置,对象由Spring容器创建,但是交由servlet容器进行管理--> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!--表示bean的生命周期由servlet来管理--> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <init-param> <!--表示在spring容器中bean的id,如果不配置该属性,那么默认和该filter的name一致--> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> <filter-mapping> <filter-name>shiro</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </filter>

编写shiro的配置文件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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置securityManager--> <property name="securityManager" ref="securityManager"/> <!--当访问需要认证的资源时,如果没有认证,那么去到登录的url,如果不配置该属性默认情况下会到根路径下的login.jsp--> <property name="loginUrl" value="/login"/> <!--配置认证成功后跳转到哪个url上,通常不设置,如果不设置,那么默认认证成功后跳转到上一个url--> <property name="successUrl" value="/index"/> <!--配置用户没有权限访问资源时跳转的页面--> <property name="unauthorizedUrl" value="/refuse"/> <!--配置shiro的过滤器链--> <property name="filterChainDefinitions"> <value> /toLogin=anon /login=authc /logout=logout /js/**=anon /css/**=anon /image/**=anon /**=anon </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> </bean> <bean id="userRealm" class="com.hust.xiaokaige.realm.UserRealm"/> </beans>

修改Controller

//登录 @Request Mapping("/login") public Model And View login(HttpServletRequest request) { ModelAndView mv=new ModelAndView("login"); String className=(String) request.getAttribute("shiroLoginFailure"); if(Unknown Account Exception.class.getName() .equals(className) ) { //抛出自定义异常 mv.addObject("msg","用户名或密码错误"); }else if(IncorrectCredentialsException.class.getName().equals(className)){ mv.addObject("msg","用户名或密码错误"); }else{ mv.addObject("msg","服务器忙") } return; } //认证通过 @Request Mapping("/index") public Model And View login(HttpSession session) { Subject subject = SecurityUtils.getSubject(); User user = subject.getPrincipal(); session.setAttribute("loginUser",user); return new ModelAndView("index"); }

添加自定义Realm

package com.hust.xiaokaige.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class UserRealm extends AuthorizingRealm { //认证 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = token.getPrincipal().toString(); //去数据库中根据username查询出用户user //设置user的菜单 return new SimpleAuthenticationInfo(user,user.getPwd(),getName()); } //授权 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { return null; } }

加入自定义凭证匹配器

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置securityManager--> <property name="securityManager" ref="securityManager"/> <!--当访问需要认证的资源时,如果没有认证,那么去到登录的url,如果不配置该属性默认情况下会到根路径下的login.jsp--> <property name="loginUrl" value="/login"/> <!--配置认证成功后跳转到哪个url上,通常不设置,如果不设置,那么默认认证成功后跳转到上一个url--> <property name="successUrl" value="/index"/> <!--配置用户没有权限访问资源时跳转的页面--> <property name="unauthorizedUrl" value="/refuse"/> <!--配置shiro的过滤器链--> <property name="filterChainDefinitions"> <value> /toLogin=anon /login=anon /logout=logout /js/**=anon /css/**=anon /image/**=anon /**=anon </value> </property> </bean> <!--配置authc过滤器,可以将表单中的属性名称改成自己想要的--> <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> </bean> <!--配置logout过滤器--> <bean id="logout" class="org.apache.shiro.web.filter.authc.logoutFilter"> <property name="redirectUrl" value="/toLogin"></property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> </bean> <bean id="userRealm" class="com.hust.xiaokaige.realm.UserRealm"> <property name="credentialsMatcher" ref="credentialMatcher"/> </bean> <!--配置凭证匹配器--> <bean id="credentialMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> </bean> </beans>

授权

开启Shiro注解模式

<!--开启Spring的AOP代理--> <aop:config proxy-target-class="true"/> <!--开启Shiro支持注解模式--> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>

使用Shiro过滤器进行配置

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置securityManager--> <property name="securityManager" ref="securityManager"/> <!--当访问需要认证的资源时,如果没有认证,那么去到登录的url,如果不配置该属性默认情况下会到根路径下的login.jsp--> <property name="loginUrl" value="/login"/> <!--配置认证成功后跳转到哪个url上,通常不设置,如果不设置,那么默认认证成功后跳转到上一个url--> <property name="successUrl" value="/index"/> <!--配置用户没有权限访问资源时跳转的页面--> <property name="unauthorizedUrl" value="/refuse"/> <!--配置shiro的过滤器链--> <property name="filterChainDefinitions"> <value> /toLogin=anon <!--使用这个perms即可--> /roles/list=perms["role:list"] /login=anon /logout=logout /js/**=anon /css/**=anon /image/**=anon /**=anon </value> </property> </bean>

SpringMVC处理异常

<!--SpringMVC处理异常--> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!--授权异常--> <prop key="org.apache.shiro.authz.UnauthorizedException">login</prop> <!--认证异常--> <prop key="org.apache.shiro.authc.AuthenticationException">refuse</prop> </props> </property> </bean>

还要在controller上添加@Permissions注解

使用shiro标签对页面的功能进行控制

导入Shiro标签库

<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>

然后在需要检查权限的地方添加shiro标签

缓存管理

每次权限检查都会到数据库中获取权限,这样效率很低,可以通过设置缓存来解决该问题。Shiro可以和ehcache或者redis集成,在这里使用ehcache来缓存数据

导入ehcache的依赖,shiro默认集成了一个ehcache的配置文件。也可以自己添加一个进行配置,如果自定义配置文件,放入src目录下

<!--ehcache依赖--> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.5.0</version> </dependency>

添加ehcache配置文件

<ehcache> <diskStore path="java.io.tmpdir/shiro-ehcache"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> </ehcache>

在shiro.xml配置文件中,添加cacheManager的配置

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> <property name="cacheManager" ref="cacheManager"/> </bean> <!--配置缓存管理器--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean>

如果在运行过程中,主体的权限发生了变化,那么应该从spring容器中调用realm中清理缓存方法,进行清理;

在进行权限变更的地方获取realm对象,调用一下clearCache的方法进行缓存清理

//清理缓存的方法 @Override public void clearCache(PrincipalCollection principals) { 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 name="cacheManager" ref="cacheManager"/> <property name="sessionManager" ref="sessionManager"/> </bean> <!--配置会话管理器--> <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager"> <!--失效时间--> <property name="globalSessionTimeout" value="300000"/> <!--删除无效session--> <property name="deleteInvalidSessions" value="true"/> </bean>

Remember me

修改表单属性配置

<!--配置authc过滤器--> <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> <property name="rememberMeParam" value="rememberMe"/> </bean> <!--记住我配置--> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!--cookie的存活时间--> <property name="maxAge" value="#{7*24*86400}"/> <!--设置cookie的名称,User需要被序列化,引用的对象也要被序列化--> <property name="name" value="rememberMe"/> </bean>

还要使用过滤器标明让哪些资源具有记住我的功能

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置securityManager--> <property name="securityManager" ref="securityManager"/> <!--当访问需要认证的资源时,如果没有认证,那么去到登录的url,如果不配置该属性默认情况下会到根路径下的login.jsp--> <property name="loginUrl" value="/toLogin"/> <!--配置认证成功后跳转到哪个url上,通常不设置,如果不设置,那么默认认证成功后跳转到上一个url--> <property name="successUrl" value="/home"/> <!--配置用户没有权限访问资源时跳转的页面--> <property name="unauthorizedUrl" value="/refuse"/> <!--配置shiro的过滤器链--> <property name="filterChainDefinitions"> <value> /toLogin=anon /login=anon /logout=logout /js/**=anon /css/**=anon /image/**=anon /index = user /**=authc </value> </property> </bean>
最新回复(0)