SpringBoot - 安全框架Shiro的整合与使用教程(附样例)
Apache Shiro 是一个开源的轻量级的 Java 安全框架,它提供身份验证、授权、密码管理以及会话管理等功能。相对于 Spring Security,Shiro 框架更加直观、易用,同时也能提供健壮的安全性。
在传统的 SSM 框架中,手动整合 Shiro 的配置步骤还是比较多的,针对 Spring Boot,Shiro 官方提供了 shiro-spring-boot-web-starter 用来简化 Shiro 在 Spring Boot 中的配置。下面通过样例进行演示。
1,添加依赖
首先编辑项目的 pom.xml 文件,添加 Shiro 依赖以及页面模板依赖:- shiro-spring-boot-web-starter:Shiro 依赖
- spring-boot-starter-thymeleaf:Thymeleaf 依赖,本样例使用了 Thymeleaf 模版。
- thymeleaf-extras-shiro:实现在 Thymeleaf 中使用 shiro 标签。
注意:
这里不需要添加 spring-boot-starter-web 依赖,因为 shiro-spring-boot-web-starter 中已经依赖了 spring-boot-starter-web
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency> <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>
2,配置 Shiro
(1)首先在 application.properties 中配置 Shrio 的基本信息:# 开启 Shrio 配置,默认为 true
shiro.enabled=true
# 开启 Shrio Web 配置,默认为 true
shiro.web.enabled=true
# 配置登录地址,默认为"login.jsp"
shiro.loginUrl=/login
# 配置登录成功地址,默认为"/"
shiro.successUrl=/index
# 配置未获授权默认跳转地址
shiro.unauthorizedUrl=/unauthorized
# 是否允许通过 URL 参数实现会话跟踪,默认为 true。如果网站支持 Cookie,可以关闭次选项。
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# 是否允许通过 Cookie 实现会话跟踪,默认为 true。
shiro.sessionManager.sessionIdCookieEnabled=true
(2)接着在 Java 代码中配置 Shrio,其中最关键的是开头提供的两个 Bean:
@Configuration public class ShiroConfig { // 配置用户和角色,Realm 可以是自定义的 Realm,也可以是 Shiro 提供的 Realm @Bean public Realm realm() { // 为简单起见,本案例没有配置数据库连接,直接配置了两个用户,以及对应的角色 TextConfigurationRealm realm = new TextConfigurationRealm(); realm.setUserDefinitions("admin=123,admin\n hangge=123,user"); // 配置角色权限,admin 具有read、write 权限,user 只有 read 权限 realm.setRoleDefinitions("admin=read,write\n user=read"); return realm; } // 配置基本的过滤规则 @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // /login 和 /doLogin 可以匿名访问 chainDefinition.addPathDefinition("/login", "anon"); chainDefinition.addPathDefinition("/doLogin", "anon"); // /logout 是一个注销登录请求 chainDefinition.addPathDefinition("/logout", "logout"); // 其余请求则都需要人周后才能访问 chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; } // 这个Bean是为了支持在Thymeleaf中使用Shiro标签 // 如果不在Thymeleaf中使用Shiro标签,那么可以不提供 ShiroDialect @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } // 这个Bean的作用是使得@RequiresRoles和@RequiresPermissions注解生效 @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // setUsePrefix(true)用于解决一个奇怪的bug。在引入spring aop的情况下。 // 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求, // 导致返回404,加入这项配置能解决这个bug defaultAdvisorAutoProxyCreator.setUsePrefix(true); return defaultAdvisorAutoProxyCreator; } }
3,配置登录接口以及页面访问接口
(1)创建一个 Controller 用于配置登录接口以及相关页面的访问接口:
@Controller public class UserController { // 登录接口 @PostMapping("/doLogin") public String doLogin(String username, String password, Model model) { // 通过接收到的用户名和密码构造一个 UsernamePasswordToken 实例 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 获取一个Subject对象 Subject subject = SecurityUtils.getSubject(); try { // 执行登录操作 subject.login(token); } catch (AuthenticationException e) { // 登录操作执行过程中,当有异常抛出时,说明登录失败,携带错误信息返回登录视图 model.addAttribute("error", "用户名或密码输入错误!"); return "login"; } // 当登录成功时,则重定向到"/index" return "redirect:/index"; } // 暴露"/admin"接口,并且该接口需要具有admin角色才能访问 @RequiresRoles("admin") @GetMapping("/admin") public String admin() { return "admin"; } // 暴露"/user"接口,并且该接口只要有admin或者user角色就能访问 @RequiresRoles(value = {"admin","user"},logical = Logical.OR) @GetMapping("/user") public String user() { return "user"; } }
(2)对于其他不需要角色就能访问的接口,直接在 WebMvc 中配置即可:
@Configuration public class WebMvcConfig implements WebMvcConfigurer{ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/index").setViewName("index"); registry.addViewController("/unauthorized").setViewName("unauthorized"); } }
4,创建全局异常处理器
全局异常处理器可以进行全局异常处理,本样例主要是处理授权异常:@ControllerAdvice public class ExceptionController { // 当用户访问未授权的资源时,跳转到 unauthorized 视图,并携带出错信息 @ExceptionHandler(AuthorizationException.class) public ModelAndView error(AuthorizationException e) { ModelAndView mv = new ModelAndView("unauthorized"); mv.addObject("error", e.getMessage()); return mv; } }
5,创建 HTML 页面
(1)login.html 是一个普通的登录页面,登录失败时通过一个 div 显示登录失败信息:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <form action="/doLogin" method="post"> <input type="text" name="username"><br> <input type="password" name="password"><br> <div th:text="${error}"></div> <input type="submit" value="登录"> </form> </div> </body>
(2)index.html 是登录成功后的首先展示当前登录用户的登录名,以及一个“注销登录”链接,同时:
- 如果当前登录用户具备“admin”角色,则展示一个“管理员页面”的超链接
- 如果当前用户具备“admin”或者“user”角色,则展示一个“普通用户页面”的超链接
<!DOCTYPE html> <html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>Hello, <shiro:principal/></h3> <h3><a href="/logout">注销登录</a></h3> <h3><a shiro:hasRole="admin" href="/admin">管理员页面</a></h3> <h3><a shiro:hasAnyRoles="admin,user" href="/user">普通用户页面</a></h3> </body> </html>
(3)admin.html 是一个普通的管理员信息展示页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>管理员页面</h1> </body> </html>
(4)user.html 是一个普通的用户信息展示页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>普通用户页面</h1> </body> </html>
(5)unauthorized.html 是一个授权失败的展示页面,该页面还会展示授权出错的信息。
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <h3>未获授权,非法访问</h3> <h3 th:text="${error}"></h3> </div> </body> </html>
6,运行测试
(1)启动项目,访问登录页(随便访问任意一个接口都会跳到登录页):

(2)使用 hangge 用户登录,由于该用户不具备 admin 角色,因此登录成功后的页面没有“管理员页面”超链接:


