前言 由于我之前学开发的时候所用的安全框架是Spring Security,没有接触过Shiro这个框架,后面需要学习Shiro的若干反序列化漏洞,遂今天来补一下简单的开发知识
什么是Shiro Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话 Management 解决方案。
环境搭建 jdk:18
SpringBoot版本:3.6
IDEA选择Spring Boot作为脚手架
发现SpringBoot的官方没有整合Shiro框架,遂勾几个常用的库无脑下一步
Maven导入
踩坑警告!!!
网上大部分博客所教程的都是SpringBoot2的版本,在SpringBoot3后强制使用JDK17从jdk8以后javax.servlet的相关代码包名改成了jakarta.servlet,shiro-spring的版本按照各大博客或者狂神的视频copy的都是低版本,在ShiroFilter中因引入的servlet不一致,导致无法解析,所以一定要把旧的sevlet相关依赖删掉
Maven中手动导入Shiro的依赖
SpringBoot3一定要导这个!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <classifier > jakarta</classifier > <version > 2.0.1</version > <exclusions > <exclusion > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > </exclusion > <exclusion > <groupId > org.apache.shiro</groupId > <artifactId > shiro-web</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <classifier > jakarta</classifier > <version > 2.0.1</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-web</artifactId > <classifier > jakarta</classifier > <version > 2.0.1</version > <exclusions > <exclusion > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > </exclusion > </exclusions > </dependency >
Controller&&index 我们需要一个页面进行Shiro的测试,所以这里新建一个Controller,并解析到相应的页面
新建一个index页面
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 喵喵</h1 > <p th:text ="${msg}" > </p > <hr > <a th:href ="@{/user/add}" > add</a > | <a th:href ="@{/user/update}" > update</a > </body > </html >
新建一个add.html页面
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > add</h1 > </body > </html >
新建一个update.htmp页面
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > update</h1 > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Controller public class TestController { @RequestMapping({"/", "/index"}) public String ToIndex (Model model) { model.addAttribute("msg" ,"Hello,Shiro!" ); return "index" ; } @RequestMapping("/user/add") public String add () { return "add" ; } @RequestMapping("/user/update") public String update () { return "update" ; } }
ShiroConfig 与SpringSecuity相同的是,我们需要为它创建COnfig配置文件
新建一个ShiroConfig
1 2 3 4 5 6 7 8 public class ShiroConfig { }
然后定义一个realm实体类,它需要继承AuthorizingRealm类,并重写两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public class UserRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { return null ; } }
很眼熟,和SpringSecurity差不多嘛
现在重新再在写ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean (); bean.setSecurityManager(defaultWebSecurityManager); return bean; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager (@Qualifier UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager (); securityManager.setRealm(userRealm); return securityManager; } @Bean public UserRealm userRealm () { return new UserRealm (); } }
这比SpringSecuity复杂多了,配置好多…
实现登录拦截 要实现登录拦截,要在getShiroFilterFactoryBean里添加代码,我们得先知道权限的管理
1 2 3 4 5 Map<String, String> filterMap = new LinkedHashMap <>(); filterMap.put("/user/add" , "authc" ); filterMap.put("/user/update" , "authc" ); bean.setFilterChainDefinitionMap(filterMap);
在getShiroFilterFactoryBean里添加如上代码后,就成功的拦截了请求
但是要注意的是Shiro不像SpringSecurity有默认的登录界面,我们得手动写一个,这里就写一个最简单的登录框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 登录页面喵</title > </head > <body > <h1 > 登录喵</h1 > <hr > <form action ="" > <p > 用户名:<input type ="text" name ="username" > </p > <p > 密码:<input type ="text" name ="password" > </p > <p > 密码:<input type ="submit" > </p > </form > </body > </html >
Controller里添加
1 2 3 4 @RequestMapping("/toLogin") public String toLogin () { return "login" ; }
现在当我们访问拦截的请求,就会自动重定向到我们的页面
实现用户认证 Shiro很多的东西都会放到Realm 里,权限操作也就是在Realm里
编辑一个Mapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RequestMapping("/login") public String login (String username, String password, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken (username, password); try { subject.login(usernamePasswordToken); return "index" ; } catch (UnknownAccountException e) { model.addAttribute("msg" , "用户名或者密码错误" ); return "login" ; } }
然后改一下前端页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org>" > <head > <meta charset ="UTF-8" > <title > 登录页面喵</title > </head > <body > <h1 > 登录喵</h1 > <hr > <form th:action ="@{/login}" > <p th:text = "${msg}" style ="color: red" > </p > <p > 用户名:<input type ="text" name ="username" > </p > <p > 密码:<input type ="text" name ="password" > </p > <p > 密码:<input type ="submit" > </p > </form > </body > </html >
这里点了一下提交后会发现执行了UserRealm里的方法,现在我们就可以在UserRealm里实现具体方法了
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证" ); String name = "test" ; String password = "123456" ; UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; if (!token.getUsername().equals(name)) { return null ; } return new SimpleAuthenticationInfo ("" , password, "" ); }
Shiro整合数据库 接下来连接数据库,密码从数据库里面取
数据库相关环境 Maven里数据库Mysql驱动我一开始导过了
现在导入一下Mybatis的包,以及我习惯用的Lambox
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 3.0.0</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.36</version > </dependency >
这里我用以前我SpringSecurity时候创的表格,因为这个密码是SpringSecurity加密的,这里我改成123456
配置数据库连接 配置application.yml
创建mapper软件包,在SpringBoot启动文件上添加包扫描,当然也可以在Mapper文件上添加@Mapper注解,但是推荐还是直接包扫描来的方便
创建UserMapper接口:
1 2 3 4 5 6 7 8 9 10 package com.study.shirostudy.mapper;import com.study.shirostudy.unti.User;import org.apache.ibatis.annotations.Select;public interface UserMapper {@Select("SELECT * FROM user where username = #{username}") User queryUserByName (String username) ; }
创建实体类User: 1 2 3 4 5 6 7 8 src/main/java/com/study/shirostudy/unti/User.java @Repository @Data public class User { int id; String username; String password; }
UserService&&impl 1 2 3 4 public interface UserService { public User queryUserByName (String username) ; }
1 2 3 4 5 6 7 8 9 10 11 @Service public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public User queryUserByName (String username) { return userMapper.queryUserByName(username); } }
Test 1 2 3 4 5 6 7 8 9 10 @SpringBootTest class ShiroStudyApplicationTests { @Autowired UserService mapper; @Test void contextLoads () { System.out.println(mapper.queryUserByName("admin" )); }
测试通过后,就可以把实际业务写入了
Shiro用户授权 ShiroConfig
中的 getShiroFilterFactoryBean
方法添加认证代码
然后在Controller添加上未授权页面
ShiroConfig
中的 getShiroFilterFactoryBean
方法中添加
1 2 bean.setUnauthorizedUrl("/noauth" );
UserRealm 类的修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.study.shirostudy.controller;import com.study.shirostudy.Service.UserService;import com.study.shirostudy.unti.User;import jakarta.annotation.Resource;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.Subject;public class UserRealm extends AuthorizingRealm { @Resource UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { System.out.println("执行授权" ); return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证" ); UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; User user = userService.queryUserByName(userToken.getUsername()); if (user == null ) { return null ; } return new SimpleAuthenticationInfo ("" , user.getPassword(), "" ); } }