本文共 10383 字,大约阅读时间需要 34 分钟。
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
以打飞机举例子:
【认证】你要登机,你需要出示你的 passport 和 ticket,passport 是为了证明你张三确实是你张三,这就是 authentication。
【授权】而机票是为了证明你张三确实买了票可以上飞机,这就是 authorization。以论坛举例子:
【认证】你要登录论坛,输入用户名张三,密码 1234,密码正确,证明你张三确实是张三,这就是 authentication。
【授权】再一 check 用户张三是个版主,所以有权限加精删别人帖,这就是 authorization 。2.1 引入依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security
2.2 ShiroConfig
创建 ShiroConfig 抽象类,实现 Shiro 的自定义配置。代码如下:
@Configurationpublic class ShiroConfig {// ShiroConfig.java @Bean public Realm realm() { // 创建 SimpleAccountRealm 对象 SimpleAccountRealm realm = new SimpleAccountRealm(); // 添加两个用户。参数分别是 username、password、roles 。 realm.addAccount("admin", "admin", "ADMIN"); realm.addAccount("normal", "normal", "NORMAL"); return realm; } @Bean public DefaultWebSecurityManager securityManager(){ // 创建 DefaultWebSecurityManager 对象 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置其使用的 Realm securityManager.setRealm(this.realm()); return securityManager; } // ShiroConfig.java @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { // <1> 创建 ShiroFilterFactoryBean 对象,用于创建 ShiroFilter 过滤器 ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // <2> 设置 SecurityManager filterFactoryBean.setSecurityManager(this.securityManager()); // <3> 设置 URL 们 filterFactoryBean.setLoginUrl("/login"); // 登录 URL filterFactoryBean.setSuccessUrl("/login_success"); // 登录成功 URL filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 无权限 URL // <4> 设置 URL 的权限配置 filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap()); return filterFactoryBean; }// ShiroConfig.java private MapfilterChainDefinitionMap() { Map filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,顺序匹配 filterMap.put("/test/echo", "anon"); // 允许匿名访问 filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色 filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色 filterMap.put("/logout", "logout"); // 退出 filterMap.put("/**", "authc"); // 默认剩余的 URL ,需要经过认证 return filterMap; }}
2.2.1 Realm
我们先来看看 Realm 的定义。
Realm 是可以访问程序特定的安全数据如用户、角色、权限等的一个组件。Realm 会将这些程序特定的安全数据转换成一种 Shiro 可以理解的形式,Shiro 就可以依次提供容易理解的 Subject 程序API而不管有多少数据源或者程序中你的数据如何组织。
Realm 通常和数据源是一对一的对应关系,如关系数据库,LDAP 目录,文件系统,或其他类似资源。因此,Realm 接口的实现使用数据源特定的API 来展示授权数据(角色,权限等),如JDBC,文件IO,Hibernate 或JPA,或其他数据访问API。
Realm 实质上就是一个特定安全的 DAO
因为这些数据源大多通常存储身份验证数据(如密码的凭证)以及授权数据(如角色或权限),每个 Shiro Realm 能够执行身份验证和授权操作。
其实“身份验证”(认证)和“授权”,这个就是 Realm 的职责。
Realm 整体的类图如下:
本示例中,在 #realm() 方法,我们创建了 SimpleAccountRealm Bean 对象。代码如下:@Bean public Realm realm() { // 创建 SimpleAccountRealm 对象 SimpleAccountRealm realm = new SimpleAccountRealm(); // 添加两个用户。参数分别是 username、password、roles 。 realm.addAccount("admin", "admin", "ADMIN"); realm.addAccount("normal", "normal", "NORMAL"); return realm; }
2.2.2 SecurityManager
本示例中,在 #securityManager() 方法,我们创建了 DefaultWebSecurityManager Bean 对象。代码如下:
@Bean public DefaultWebSecurityManager securityManager(){ // 创建 DefaultWebSecurityManager 对象 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置其使用的 Realm securityManager.setRealm(this.realm()); return securityManager; }
2.2.3 ShiroFilter
通过 AbstractShiroFilter 过滤器,实现对请求的拦截,从而实现 Shiro 的功能。AbstractShiroFilter 整体的类图如下:
示例中,在 #shiroFilterFactoryBean() 方法,我们创建了 ShiroFilterFactoryBean Bean 对象。代码如下:@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { // <1> 创建 ShiroFilterFactoryBean 对象,用于创建 ShiroFilter 过滤器 ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // <2> 设置 SecurityManager filterFactoryBean.setSecurityManager(this.securityManager()); // <3> 设置 URL 们 filterFactoryBean.setLoginUrl("/login"); // 登录 URL filterFactoryBean.setSuccessUrl("/login_success"); // 登录成功 URL filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 无权限 URL // <4> 设置 URL 的权限配置 filterFactoryBean.setFilterChainDefinitionMap(this.filterChainDefinitionMap()); return filterFactoryBean; }
<1> 处,创建 ShiroFilterFactoryBean 对象,用于创建 SpringShiroFilter 过滤器。
<2> 处,设置其 SecurityManager 属性。
<3> 处,设置各种 URL 。
#setLoginUrl(String loginUrl) 方法,设置登录 URL 。在 Shiro 中,约定 GET loginUrl 为登录页面,POST loginUrl 为登录请求。
#setSuccessUrl(String successUrl) 方法,设置登录成功 URL 。在登录成功时,会重定向到该 URL 上。 #etUnauthorizedUrl(String unauthorizedUrl) 方法,设置无权限的 URL 。在请求校验权限不通过时,会重定向到该 URL 上。 上述的 URL 对应的接口,都需要我们自己来实现。具体可见「2.3 SecurityController」小节。<4> 处,调用 #setFilterChainDefinitionMap(Map<String, String>
filterChainDefinitionMap) 方法,设置 URL 的权限配置。下面,让我们回过头来看看 #filterChainDefinitionMap() 方法的具体 URL 的权限配置。代码如下:
private MapfilterChainDefinitionMap() { Map filterMap = new LinkedHashMap<>(); // 注意要使用有序的 LinkedHashMap ,顺序匹配 filterMap.put("/test/echo", "anon"); // 允许匿名访问 filterMap.put("/test/admin", "roles[ADMIN]"); // 需要 ADMIN 角色 filterMap.put("/test/normal", "roles[NORMAL]"); // 需要 NORMAL 角色 filterMap.put("/logout", "logout"); // 退出 filterMap.put("/**", "authc"); // 默认剩余的 URL ,需要经过认证 return filterMap; }
另外,这里在补充一点,请求在 ShiroFilter 拦截之后,会根据该请求的情况,匹配到配置的内置的 Shiro Filter 们,逐个进行处理。也就是说,ShiroFilter 实际内部有一个由 内置的 Shiro Filter 组成的过滤器链。
2.3 SecurityController
在 cn.iocoder.springboot.lab01.shirodemo.controller 包路径下,创建 SecurityController 类,提供登录、登录成功等接口。代码如下:
// SecurityController.java@Controller@RequestMapping("/")public class SecurityController { private Logger logger = LoggerFactory.getLogger(getClass()); @GetMapping("/login") public String loginPage() { /**省略代码**/ } @ResponseBody @PostMapping("/login") public String login(HttpServletRequest request) { /**省略代码**/ } @ResponseBody @GetMapping("/login_success") public String loginSuccess() { /**省略代码**/ } @ResponseBody @GetMapping("/unauthorized") public String unauthorized() { /**省略代码**/ }}
GET /login 地址,跳转登录页面。代码如下:
@GetMapping("/login") public String loginPage() { return "login"; }
登录页面
2.3.2 登录请求
对于登录请求,会被我们配置的 Shiro FormAuthenticationFilter 过滤器进行拦截,进行用户的身份认证。整个过程如下:
所以,POST loginUrl 的目的,实际是为了处理认真失败的情况。也因此,POST login 地址,实现代码如下:
@PostMapping("/login") public String login(Model model,HttpServletRequest request) { // <1> 判断是否已经登录 Subject subject = SecurityUtils.getSubject(); if (subject.getPrincipal() != null) { return "index2"; } // <2> 获得登录失败的原因 String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); // 翻译成人类看的懂的提示 String msg = ""; if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) { msg = "账号不存在"; } else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) { msg = "密码不正确"; } else if (LockedAccountException.class.getName().equals(shiroLoginFailure)) { msg = "账号被锁定"; } else if (ExpiredCredentialsException.class.getName().equals(shiroLoginFailure)) { msg = "账号已过期"; } else { msg = "未知";// logger.error("[login][未知登录错误:{}]", shiroLoginFailure); } model.addAttribute("msg",msg); return "error"; }
ERROR页面:
ERROR
2.3.3 登录成功
GET login_success 地址,登录成功响应。代码如下:
@GetMapping("/login_success") public String loginSuccess() { return "list"; }
list就是成功页面,随便写就好了~
2.3.4 未授权
GET unauthorized 地址,未授权响应。代码如下:
@GetMapping("/unauthorized") public String unauthorized() { return "unauthorized"; }
2.4 TestController
创建 TestController 类,提供测试 API 接口。代码如下:
@Controller@RequestMapping("/test")public class TestController { @GetMapping("/demo") public String demo() { return "示例返回"; } @GetMapping("/echo") public String home() { return "echo"; } @GetMapping("/admin") public String admin() { return "admin"; } @GetMapping("/normal") public String normal() { return "normal"; }}
2.5 Application
@SpringBootApplicationpublic class ShiroApplication { public static void main(String[] args) { SpringApplication.run(ShiroApplication.class, args); }}
至此,我们已经完成了 Shiro 的入门。美滋滋,大家可以自己多多测试一下。
转载地址:http://gmmwb.baihongyu.com/