shiro实战教程笔记

    技术2022-07-11  87

    文章目录

    1. 权限管理1.1 什么是权限管理1.2 什么是身份认证1.3 什么是身份授权 2. 什么是shiro3. shiro核心架构3.1 Subject3.2 SecurityManager3.3 Authenticator3.4 Authorizer3.5 Realm3.6 SessionManager3.7 SessionDao3.8 CacheManager3.9 Cryptography 4. shiro的认证4.1 认证4.2 认证的关键对象4.3 认证流程4.4 认证的开发4.5 自定义Realm的开发4.6 对密码进行加盐和hash 5. shiro的授权6. spring boot整合shiro1. spring boot前期准备2. **shrio框架的整合!**3. 工具类和实体类4. web层开发5. 集成验证码 7. spring boot整合shiro之thymeleaf权限控制

    1. 权限管理

    1.1 什么是权限管理

    基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或安全策略控制用户可以访问并且只能访问被授权的资源。

    1.2 什么是身份认证

    身份认证 就是判断一个用户是否为合法用户的过程,最简单方式就是根据用户输入的用户名和密码,和系统存储层一致不,来判断用户身份是否正确。

    1.3 什么是身份授权

    身份授权 即访问控制,控制谁能访问那些资源,不同的用户应该拥有不同的资源访问权限,常见的有学校的教务管理系统:有教师,学生,管理员登录几个模块,以不同的身份登录就会显示不同的界面!

    2. 什么是shiro

    shiro是Apache旗下的一个开源框架,它将软件系统的安全认证相关的功能抽取出来,是一个功能强大且简单易用的Java安全框架,它可以用来进行身份验证,授权,加密和会话管理。

    3. shiro核心架构

    3.1 Subject

    Subject即主体,外部应用与Subject进行交互,Subject对象记录了当前操作的用户,将用户的概念理解成当前操作的主体,可能是一个浏览器的请求的用户,也可能是一个允许的程序。Subject在shiro中是一个接口,接口中定义了很多认证授权的方法,外部程序通过Subject对象进行认证和授权,而Subject对象是通过SecurityManager进行认证和授权!

    3.2 SecurityManager

    SecurityManager即安全管理器,对全部的Subject对象进行安全管理,它是shiro的核心,通过SecurityManager可以完成对Subject对象的认证授权等系列操作,实质上是使用Authenticator进行认证,使用Authorizer进行授权,使用SessionManager进行会话管理!

    3.3 Authenticator

    Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供了ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足认证的大部分需求,也可以使用自定义的认证器。

    3.4 Authorizer

    Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否由此功能的操作权限。

    3.5 Realm

    Realm即领域,相当于datasource,SecurityManager进行安全认证是需要从Realm获取用户权限数据,比如:用户的身份信息如果放到数据库中,那么就需要从数据库中获取到用户身份信息。

    注:不要把Realm认为只是在里面取数据,在Realm还有认证和授权相关的代码!

    3.6 SessionManager

    SessionManager即会话管理,shiro框架定义了一系列会话管理,它不依赖于web的session,所以shiro可以使用在非web的环境下,也可以将分布式的应用集中在一起管理,此特性可以实现单点登录!

    3.7 SessionDao

    SessionDao即会话dao,是对session会话操作的一套接口,比如将session存储到数据库,也可以使用jdbc将session存入数据库。

    3.8 CacheManager

    CacheManager即缓存管理器,将用户权限数据存储到缓存中,可以提高性能。

    3.9 Cryptography

    Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发,比如提供了常用分散列,盐值计算,加/解密等功能!

    4. shiro的认证

    4.1 认证

    认证就是判断一个用户是否为合法用户的过程,最简单方式就是根据用户输入的用户名和密码,和系统存储层一致不,来判断用户身份是否正确。

    4.2 认证的关键对象

    Subject:主体

    访问系统的用户,主体可以是程序,用户等,进行认证的都称为主体。

    Principal:身份信息

    是主体进行身份认证的标识,标识必须具有唯一性,如电话号码,手机号,邮箱地址,一个主体可以有多个身份,但必须有一个主身份!

    credential:凭证信息

    只有主体自己知道的安全信息,如密码,证书等。

    4.3 认证流程

    4.4 认证的开发

    使用本地shiro.ini文件开发

    创建maven项目引入依赖 <!--引入shiro相关依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.5.3</version> </dependency> 引入shiro.ini配置文件 开发认证代码 /** * 测试认证(加载本地的realm) * */ public class TestAuthenticator { public static void main( String[] args ) { //1.创建SecurityManager对象 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini")); //可以从数据库中获取realm //2.将安装工具类设置为默认的安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //3.获取主体对象 Subject subject = SecurityUtils.getSubject(); //4.创建token令牌 UsernamePasswordToken token = new UsernamePasswordToken("liuzeyu","809080"); //5.执行登录方法 try { subject.login(token); System.out.println("登陆成功!!"); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名错误!!"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误!!"); } } }

    4.5 自定义Realm的开发

    使用本地shiro.ini开发,使用的realm就是我们本地提供的,而实际开发中,realm的数据往往是我们的数据库中读取!

    shiro提供的Realm

    Realm的实现类中认证是使用SimpleAccountRealm SimpleAccountRealm 部分源码中有两个方法,分别是认证:doGetAuthenticationInfo(AuthenticationToken token),授权:doGetAuthorizationInfo(PrincipalCollection principals)

    //判断用户是否为合法用户 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken)token; SimpleAccount account = this.getUser(upToken.getUsername()); if (account != null) { if (account.isLocked()) { throw new LockedAccountException("Account [" + account + "] is locked."); } if (account.isCredentialsExpired()) { String msg = "The credentials for account [" + account + "] are expired"; throw new ExpiredCredentialsException(msg); } } return account; } //为合法用户授权 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = this.getUsername(principals); this.USERS_LOCK.readLock().lock(); AuthorizationInfo var3; try { var3 = (AuthorizationInfo)this.users.get(username); } finally { this.USERS_LOCK.readLock().unlock(); } return var3; } 自定义Realm /** * 自定义realm,分别实现认证和授权方法 */ public class CustomerRealm extends AuthorizingRealm { //身份认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //授权 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取用户名key String principal = (String) authenticationToken.getPrincipal(); //根据前台传递过来的principal身份信息去使用jdbc或者mybatis查询数据库 if("liuzeyu".equals(principal)){ /** * principal:返回当前数据库中的正确用户名 * credentials:返回当前数据库中正确的密码 * realmName:当前realm名字 */ return new SimpleAuthenticationInfo(principal,"809080",this.getName()); } return null; } } 使用自定义Realm进行认证 /** * 测试加载远程数据库的realm */ public class TestCustomerAuthenticator { public static void main(String[] args) { //1.创建SecurityManager DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(new CustomerRealm()); //2.将安装工具设置为默认的安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //3.获取主体对象 Subject subject = SecurityUtils.getSubject(); //4.获取令牌 UsernamePasswordToken token = new UsernamePasswordToken("liuzeyu", "809080"); //5.执行 try { subject.login(token); System.out.println("登陆成功!!"); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名错误"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } } }

    4.6 对密码进行加盐和hash

    测试加密 /** * 测试hash,salt,散列 */ public class TestHash { public static void main(String[] args) { //1.不加salt和散列次数 Md5Hash md5Hash1 = new Md5Hash("809080"); System.out.println(md5Hash1.toHex()); //转成16进制字符串 //2.加salt,不添加散列次数 Md5Hash md5Hash2 = new Md5Hash("809080","Xq*07"); System.out.println(md5Hash2.toHex()); //3.加salt,添加散列次数1024次 Md5Hash md5Hash3 = new Md5Hash("809080","Xq*07",1024); System.out.println(md5Hash3.toHex()); } } 为明文加入加密算法 public class CustomerReleamMd5 extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); //利用假数据去匹配 if("liuzeyu".equals(principal)){ return new SimpleAuthenticationInfo(principal, "641acb1f58ed7e24677a1e26d2497604", //传递不同的密码强度验证 ByteSource.Util.bytes("Xq*07"), this.getName()); } return null; } } public class TestCustomerMd5Authenticator { public static void main(String[] args) { //身份验证 //1.1创建安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //设置自定义realm获取认证数据 CustomerReleamMd5 releamMd5 = new CustomerReleamMd5(); //创建凭证匹配器 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); //(md5)算法 hashedCredentialsMatcher.setHashIterations(1024); //散列1024次,默认是一次 releamMd5.setCredentialsMatcher(hashedCredentialsMatcher); //为凭证设置凭证适配器(hash) defaultSecurityManager.setRealm(releamMd5); //1.2设置安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //1.3获取主体 Subject subject = SecurityUtils.getSubject(); //1.4获取令牌 UsernamePasswordToken token = new UsernamePasswordToken("liuzeyu","809080"); //1.5执行 try { subject.login(token); System.out.println("登陆成功"); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名错误"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } } }

    5. shiro的授权

    在自定义的Realm基础上加入授权功能,只有通过认证用户才可以被授权!

    自定义Realm public class CustomerReleamMd5 extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal(); System.out.println(primaryPrincipal); System.out.println("primaryPrincipal:"+primaryPrincipal); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole("admin"); //添加角色admin simpleAuthorizationInfo.addStringPermission("product:*:01"); //权限字符串 product 可以对01实例执行任何操作 return simpleAuthorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); //利用假数据去匹配 if("liuzeyu".equals(principal)){ return new SimpleAuthenticationInfo(principal, "641acb1f58ed7e24677a1e26d2497604", //传递不同的密码强度验证 ByteSource.Util.bytes("Xq*07"), this.getName()); } return null; } } 测试 public class TestCustomerMd5Authenticator { public static void main(String[] args) { //身份验证 //1.1创建安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); //设置自定义realm获取认证数据 CustomerReleamMd5 releamMd5 = new CustomerReleamMd5(); //创建凭证匹配器 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); //(md5)算法 hashedCredentialsMatcher.setHashIterations(1024); //散列1024次,默认是一次 releamMd5.setCredentialsMatcher(hashedCredentialsMatcher); //为凭证设置凭证适配器(hash) defaultSecurityManager.setRealm(releamMd5); //1.2设置安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); //1.3获取主体 Subject subject = SecurityUtils.getSubject(); //1.4获取令牌 UsernamePasswordToken token = new UsernamePasswordToken("liuzeyu","809080"); //1.5执行 try { subject.login(token); System.out.println("登陆成功"); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名错误"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } //2.授权 //如果认证通过 if(subject.isAuthenticated()){ //基于角色管理 boolean admin = subject.hasRole("admin"); System.out.println(admin); //基于资源管理 boolean permitted = subject.isPermitted("product:create:01"); System.out.println(permitted); } } }

    6. spring boot整合shiro

    模板引擎使用原生的JSP,配合ShiroFilter实现用户的认证和授权,实现用户注册,登录,可以以不同的身份显示不同的资源!

    1. spring boot前期准备

    导入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!-- springboot启动shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency> <!-- shiro默认缓存--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.5.3</version> </dependency> <!--redis整合springboot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 引入springboot解析jsp依赖--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 引入mybatis相关的启动依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <!-- mysql相关依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!-- druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.19</version> </dependency> </dependencies> 准备SQL文件 /* Navicat Premium Data Transfer Source Server : xiaochen的数据库 Source Server Type : MySQL Source Server Version : 50718 Source Host : 127.0.0.1:3306 Source Schema : shiro Target Server Type : MySQL Target Server Version : 50718 File Encoding : 65001 Date: 29/05/2020 16:31:22 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_perms -- ---------------------------- DROP TABLE IF EXISTS `t_perms`; CREATE TABLE `t_perms` ( `id` int(6) NOT NULL AUTO_INCREMENT, `name` varchar(80) DEFAULT NULL, `url` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_perms -- ---------------------------- BEGIN; INSERT INTO `t_perms` VALUES (1, 'user:*:*', ''); INSERT INTO `t_perms` VALUES (2, 'product:*:01', NULL); INSERT INTO `t_perms` VALUES (3, 'order:*:*', NULL); COMMIT; -- ---------------------------- -- Table structure for t_role -- ---------------------------- DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(6) NOT NULL AUTO_INCREMENT, `name` varchar(60) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_role -- ---------------------------- BEGIN; INSERT INTO `t_role` VALUES (1, 'admin'); INSERT INTO `t_role` VALUES (2, 'user'); INSERT INTO `t_role` VALUES (3, 'product'); COMMIT; -- ---------------------------- -- Table structure for t_role_perms -- ---------------------------- DROP TABLE IF EXISTS `t_role_perms`; CREATE TABLE `t_role_perms` ( `id` int(6) NOT NULL, `roleid` int(6) DEFAULT NULL, `permsid` int(6) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_role_perms -- ---------------------------- BEGIN; INSERT INTO `t_role_perms` VALUES (1, 1, 1); INSERT INTO `t_role_perms` VALUES (2, 1, 2); INSERT INTO `t_role_perms` VALUES (3, 2, 1); INSERT INTO `t_role_perms` VALUES (4, 3, 2); INSERT INTO `t_role_perms` VALUES (5, 1, 3); COMMIT; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(6) NOT NULL AUTO_INCREMENT, `username` varchar(40) DEFAULT NULL, `password` varchar(40) DEFAULT NULL, `salt` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user -- ---------------------------- BEGIN; INSERT INTO `t_user` VALUES (1, 'xiaochen', '24dce55acdcb3b6363c7eacd24e98cb7', '28qr0xu%'); INSERT INTO `t_user` VALUES (2, 'zhangsan', 'ca9f1c951ce2bfb5669f3723780487ff', 'IWd1)#or'); COMMIT; -- ---------------------------- -- Table structure for t_user_role -- ---------------------------- DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `id` int(6) NOT NULL, `userid` int(6) DEFAULT NULL, `roleid` int(6) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user_role -- ---------------------------- BEGIN; INSERT INTO `t_user_role` VALUES (1, 1, 1); INSERT INTO `t_user_role` VALUES (2, 2, 2); INSERT INTO `t_user_role` VALUES (3, 2, 3); COMMIT; SET FOREIGN_KEY_CHECKS = 1; application.properties配置文件 spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp spring.application.name=shiro server.servlet.context-path=/shiro server.port=8888 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.username=root spring.datasource.password=809080 spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&useSSL=false&characterEncoding=UTF-8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver mybatis.type-aliases-package=com.liuzeyu.entity mybatis.mapper-locations=classpath:com/liuzeyu/mapper/*.xml logging.level.com.liuzeyu.mapper=debug #相关配置 spring.redis.port=6379 spring.redis.host=localhost spring.redis.database=0 mapper层开发 @Mapper public interface UserMapper { public void save(User user); public User findByUsername(String username); public User findRolesByUsername(String username); public List<Perms> findPermsByRoleId(Integer id); }

    表和表之间的关系:

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.liuzeyu.mapper.UserMapper"> <insert id="save" useGeneratedKeys="true" parameterType="user" keyProperty="id" > insert into t_user values(#{id},#{username},#{password},#{salt}) </insert> <select id="findByUsername" parameterType="String" resultType="user"> select * from t_user where username=#{username} </select> <!--查询的是一个用户对应的多个角色信息--> <resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <collection property="roles" javaType="list" ofType="Role"> <id column="rid" property="id"/> <result column="name" property="name"/> </collection> </resultMap> <select id="findRolesByUsername" parameterType="String" resultMap="userMap"> SELECT u.id,u.username,r.id rid,r.name from t_user u left join t_user_role ur on u.id = ur.userid left join t_role r on r.id = ur.roleid where u.username = #{username} </select> <select id="findPermsByRoleId" parameterType="Integer" resultType="Perms"> select p.id,p.name,r.name from t_role r left join t_role_perms rp on rp.roleid=r.id left join t_perms p on p.id = rp.permsid where r.id=#{id} </select> </mapper> service层开发 public interface UserService { public void save(User user); public User findByUsername(String username); public User findRolesByUsername(String username); public List<Perms> findPermsByRoleId(Integer id); } @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserMapper mapper; @Override public void save(User user) { //获取盐 String salt = SaltUtils.getSalt(8); user.setSalt(salt); Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024); user.setPassword(md5Hash.toHex()); mapper.save(user); } @Override public User findByUsername(String username) { return mapper.findByUsername(username); } @Override public User findRolesByUsername(String username) { return mapper.findRolesByUsername(username); } @Override public List<Perms> findPermsByRoleId(Integer id) { return mapper.findPermsByRoleId(id); } }

    2. shrio框架的整合!

    ShiroConfig配置 @Configuration public class ShiroConfig { //1.创建ShiroFilter @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ //创建Shiro的filter ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //注入安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); //配置系统受限资源 Map<String,String> map = new HashMap<String,String>(); map.put("/user/login", "anon"); //将不需要授权的资源放最上面 map.put("/user/register", "anon"); map.put("/register.jsp","anon"); map.put("/**", "authc"); //authc请求这个资源需要认证和授权。/**表示对所有的资源认证 shiroFilterFactoryBean.setFilterChainDefinitionMap(map); //shiroFilterFactoryBean.setLoginUrl("/login.jsp"); 默认请求受限资源跳转login.jsp //配置系统公共资源 return shiroFilterFactoryBean; } //2.创建web安全管理器 @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; } @Bean("realm") //创建自定义realm public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //配置凭证适配器 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5"); hashedCredentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(hashedCredentialsMatcher); //开启shiro默认的ehcache缓存机制 customerRealm.setCachingEnabled(true); //启动缓存 customerRealm.setAuthorizationCachingEnabled(true); //启动认证缓存 customerRealm.setAuthorizationCacheName("authorizationCache"); //命名 customerRealm.setAuthenticationCachingEnabled(true); //启动授权缓存 customerRealm.setAuthenticationCacheName("authenticatonCache"); //命名 customerRealm.setCacheManager(new RedisCacheManager()); //使用Redis提供的缓存机制 return customerRealm; } } Realm开发 public class CustomerRealm extends AuthorizingRealm { @Autowired private UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal(); //假数据 // if("liuzeyu12a".equals(primaryPrincipal)){ // SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // simpleAuthorizationInfo.addRole("user"); // simpleAuthorizationInfo.addStringPermission("user:create:*"); // simpleAuthorizationInfo.addStringPermission("user:save:*"); // } User user = userService.findRolesByUsername(primaryPrincipal); if(!ObjectUtils.isEmpty(user.getRoles())){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); user.getRoles().forEach(role->{ simpleAuthorizationInfo.addRole(role.getName()); List<Perms> perms = userService.findPermsByRoleId(role.getId()); if(!CollectionUtils.isEmpty(perms)){ perms.forEach(perm->{ simpleAuthorizationInfo.addStringPermission(perm.getName()); }); } }); return simpleAuthorizationInfo; } return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); //连接数据库做查询用户名是否存在 //假数据 if("liuzeyu12a".equals(principal)){ // return new SimpleAuthenticationInfo(principal, "809080", this.getName()); // } User user = userService.findByUsername(principal); if(!ObjectUtils.isEmpty(user)){ return new SimpleAuthenticationInfo(principal, user.getPassword(), new MyByteSource(user.getSalt()), this.getName()); } return null; } } Redis缓存实现了Shiro提供的CacheManager public class RedisCacheManager implements CacheManager { // 参数:认证或者授权的名字 @Override public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException { System.out.println("缓存名称:"+cacheName); return new RedisCache<K,V>(cacheName); } } Reids缓冲器 public class RedisCache<K,V> implements Cache<K,V> { private String cacheName; public RedisCache(String cacheName) { this.cacheName = cacheName; } public RedisCache() { } @Override public V get(K k) throws CacheException { System.out.println("获取缓存:"+k); return (V) getRedisTemplate().opsForHash().get(this.cacheName, k.toString()); } @Override public V put(K k, V v) throws CacheException { System.out.println("设置缓存:"+"k:"+k+" value:"+v); getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v); return null; } @Override public V remove(K k) throws CacheException { return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString()); } @Override public void clear() throws CacheException { getRedisTemplate().delete(this.cacheName); } @Override public int size() { return getRedisTemplate().opsForHash().size(this.cacheName).intValue(); } @Override public Set<K> keys() { return getRedisTemplate().opsForHash().keys(this.cacheName); } @Override public Collection<V> values() { return getRedisTemplate().opsForHash().values(this.cacheName); } //封装获取redisTempalte private RedisTemplate getRedisTemplate(){ RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtils.getBean("redisTemplate"); //设置序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } }

    3. 工具类和实体类

    ApplicationContextUtils: /** * 功能就是从IOC容器中获取bean */ @Component public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } //根据bean名称从工厂中获取bean对象 public static Object getBean(String name){ return applicationContext.getBean(name); } } 自定义salt实现,实现序列化接口 //自定义salt实现,实现序列化接口 public class MyByteSource extends SimpleByteSource implements Serializable { public MyByteSource(String string) { super(string); } } 获取随机盐 /** * 获取随机盐 */ public class SaltUtils { public static String getSalt(int n){ StringBuilder sb = new StringBuilder(); String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz!@#$%^&*()"; char[] code = chars.toCharArray(); for (int i = 0; i < n; i++) { sb.append(code[new Random().nextInt(code.length)]); } return sb.toString(); } } 实体类 @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class User implements Serializable { private Integer id; private String username; private String password; private String salt; private List<Role> roles; } @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Role implements Serializable { private Integer id; private String name; private List<Perms> perms; } @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Perms implements Serializable { private Integer id; private String name; private String url; }

    4. web层开发

    controller层开发 @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService service; /** * 处理身份验证 * @param username * @param password * @return */ @PostMapping("/login") public String login(String username,String password){ Subject subject = SecurityUtils.getSubject(); try { subject.login(new UsernamePasswordToken(username,password)); System.out.println("登陆成功"); return "redirect:/index.jsp"; }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名不存在"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } return "redirect:/login.jsp"; } /** * 用户退出登录 * @return */ @GetMapping("logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login.jsp"; } @PostMapping("/register") public String register(User user){ try { service.save(user); System.out.println("注册成功"); return "redirect:/login.jsp"; }catch (Exception e){ e.printStackTrace(); System.out.println("注册失败"); return "redirect:/register.jsp"; } } } JSP界面

    login.jsp

    <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>登录页面</h2> <form action="${pageContext.request.contextPath}/user/login" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="text" name="password"><br> <input type="submit" value="登录"><br> </form> </body> </html>

    register.jsp

    <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>注册页面</h2> <form action="${pageContext.request.contextPath}/user/register" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="text" name="password"><br> <input type="submit" value="立即注册"><br> </form> </body> </html>

    index.jsp

    <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" language="java" %> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>系统主页</title> </head> <body> <h1>V0.1 系统主页</h1> <ul> <shiro:hasAnyRoles name="admin,user"> <li><a href="">用户管理</a> <ul> <ui> <shiro:hasPermission name="user:create:*"> <li>创建</li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:01"> <li>删处</li> </shiro:hasPermission> <shiro:hasPermission name="user:update:01"> <li>修改</li> </shiro:hasPermission> <shiro:hasPermission name="user:select:01"> <li>查询</li> </shiro:hasPermission> </ui> </ul> </li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="admin,product,order"> <shiro:hasPermission name="product:*:01"> <li><a href="">商品管理</a></li> </shiro:hasPermission> <shiro:hasPermission name="order:*:01"> <li><a href="">订单管理</a></li> </shiro:hasPermission> <li><a href="">物流管理</a></li> </shiro:hasAnyRoles> </ul> <a href="${pageContext.request.contextPath}/user/logout">退出</a> </body> </html>

    5. 集成验证码

    修改登录页面 请输入验证码:<input type="text" name="code"/><img src="${pageContext.request.contextPath}/user/getImage"> controller层开发 @PostMapping("/login") public String login(String username,String password,String code,HttpSession session){ String codes = (String) session.getAttribute("code"); try { if(codes.equalsIgnoreCase(code)){ Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username,password)); System.out.println("登陆成功"); return "redirect:/index.jsp"; }else{ throw new RuntimeException("验证码错误"); } }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名不存在"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); }catch (RuntimeException e){ e.printStackTrace(); System.out.println(e.getMessage()); } return "redirect:/login.jsp"; } @RequestMapping("/getImage") public void getImage(HttpSession session, HttpServletResponse response){ try { String verifyCode = VerifyCodeUtils.generateVerifyCode(4); session.setAttribute("code",verifyCode); ServletOutputStream os = response.getOutputStream(); //验证码存入图片 response.setContentType("image/png"); VerifyCodeUtils.outputImage(220, 60, os , verifyCode); } catch (IOException e) { e.printStackTrace(); } } 需要重新修改salt的序列化规则

    改用实现ByteSource 接口,因为SimpleByteSource 没有提供默认的无参构造,序列化会出现问题!

    //自定义salt实现,实现序列化接口 public class MyByteSource implements ByteSource , Serializable { private byte[] bytes; private String cachedHex; private String cachedBase64; public MyByteSource(){ } public MyByteSource(byte[] bytes) { this.bytes = bytes; } public MyByteSource(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public MyByteSource(String string) { this.bytes = CodecSupport.toBytes(string); } public MyByteSource(ByteSource source) { this.bytes = source.getBytes(); } public MyByteSource(File file) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(file); } public MyByteSource(InputStream stream) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream); } public static boolean isCompatible(Object o) { return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } public byte[] getBytes() { return this.bytes; } public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(this.getBytes()); } return this.cachedHex; } public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(this.getBytes()); } return this.cachedBase64; } public String toString() { return this.toBase64(); } public int hashCode() { return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0; } public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof ByteSource) { ByteSource bs = (ByteSource)o; return Arrays.equals(this.getBytes(), bs.getBytes()); } else { return false; } } private static final class BytesHelper extends CodecSupport { private BytesHelper() { } public byte[] getBytes(File file) { return this.toBytes(file); } public byte[] getBytes(InputStream stream) { return this.toBytes(stream); } } }

    7. spring boot整合shiro之thymeleaf权限控制

    在springboot整合Shiro的基础上,修改模板引擎为thymeleaf,需要注意的是thymeleaf界面的跳转要借助于控制器!

    修改application.properties 将 spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp

    修改为

    spring.thymeleaf.cache=false spring.thymeleaf.suffix=.html spring.thymeleaf.prefix=classpath:/templates/ 添加springboot的thymeleaf的启动依赖和shiro整合thymeleaf <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> 修改JSP页面为html页面

    index.html

    <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>登录页面</h2> <form th:action="@{/user/login}" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="text" name="password"><br> 请输入验证码:<input type="text" name="code"/> <input type="submit" value="登录"><br></form> <br><img th:src="@{/user/getImage}"> <a th:href="@{/user/registerview}">立即注册</a> </form> </body> </html>

    register.html

    <!DOCTYPE html> <html lang="en" xmlns:th="http:www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>注册页面</h2> <form th:action="@{/user/register}" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="text" name="password"><br> <input type="submit" value="立即注册"><br> </form> </body> </html>

    index.html

    <!DOCTYPE html > <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" xmlns:shrio="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>系统主页</title> </head> <body> <h1>V0.1 系统主页</h1> <h1><shiro:principal/></h1> <ul> <shiro:hasAnyRoles name="admin,user"> <li><a href="">用户管理</a> <ul> <ui> <shiro:hasPermission name="user:create:*"> <li>创建</li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:01"> <li>删处</li> </shiro:hasPermission> <shiro:hasPermission name="user:update:01"> <li>修改</li> </shiro:hasPermission> <shiro:hasPermission name="user:select:01"> <li>查询</li> </shiro:hasPermission> </ui> </ul> </li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="admin,product,order"> <shiro:hasPermission name="product:*:01"> <li><a href="">商品管理</a></li> </shiro:hasPermission> <shiro:hasPermission name="order:*:01"> <li><a href="">订单管理</a></li> </shiro:hasPermission> <li><a href="">物流管理</a></li> </shiro:hasAnyRoles> </ul> <hr> <p><shiro:notAuthenticated> </shiro:notAuthenticated></p> <hr> <p><shrio:authenticated/></p> <a th:href="@{/user/logout}">退出</a> </body> </html> 修改web控制器 @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService service; /** * 处理身份验证 * @param username * @param password * @return */ @PostMapping("/login") public String login(String username,String password,String code,HttpSession session){ String codes = (String) session.getAttribute("code"); try { if(codes.equalsIgnoreCase(code)){ Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username,password)); System.out.println("登陆成功"); return "redirect:/user/index"; }else{ throw new RuntimeException("验证码错误"); } }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户名不存在"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); }catch (RuntimeException e){ e.printStackTrace(); System.out.println(e.getMessage()); } return "redirect:/user/loginview"; } /** * 用户退出登录 * @return */ @GetMapping("logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/user/loginview"; } @PostMapping("/register") public String register(User user){ try { service.save(user); System.out.println("注册成功"); return "redirect:/user/loginview"; }catch (Exception e){ e.printStackTrace(); System.out.println("注册失败"); return "redirect:/user/registerview "; } } @RequestMapping("/getImage") public void getImage(HttpSession session, HttpServletResponse response){ try { String verifyCode = VerifyCodeUtils.generateVerifyCode(4); session.setAttribute("code",verifyCode); ServletOutputStream os = response.getOutputStream(); //验证码存入图片 response.setContentType("image/png"); VerifyCodeUtils.outputImage(220, 60, os , verifyCode); } catch (IOException e) { e.printStackTrace(); } } @RequestMapping("/loginview") public String loginview(){ System.out.println("正在进入登录页面..."); return "login"; } @RequestMapping("/registerview") public String registerview(){ System.out.println("正在进入注册页面..."); return "register"; } @RequestMapping("/index") public String index(){ return "index"; } } 添加springboot方言处理,否则shiro标签将失效!

    在shiroConfig.java中添加

    //加入spring boot的方言处理,否则页面标签将不起作用 @Bean(name = "shiroDialect") public ShiroDialect shiroDialect(){ return new ShiroDialect(); } 测试

    以xiaochen和zhangsan两种不同的身份登录系统!

    xiaochen: zhangsan: 比对数据库中xiaochen和zhansan对象的角色信息和权限

    根据中间表可以知道xiaochen:admin,zhangsan:user,product 测试再通过角色权限表查看: 由此可见,虽然zhangsan拥有两个角色,但是他并没有拥有订单(order)权限,所以zhangsan没有订单管理选项!!

    测试完成!!

    参考学习:https://www.bilibili.com/video/BV1uz4y197Zm?p=26

    Processed: 0.011, SQL: 9