博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Boot 安全框架 Shiro 入门
阅读量:2157 次
发布时间:2019-05-01

本文共 10383 字,大约阅读时间需要 34 分钟。

1. 概述

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

以打飞机举例子:

【认证】你要登机,你需要出示你的 passport 和 ticket,passport 是为了证明你张三确实是你张三,这就是 authentication。

【授权】而机票是为了证明你张三确实买了票可以上飞机,这就是 authorization。

以论坛举例子:

【认证】你要登录论坛,输入用户名张三,密码 1234,密码正确,证明你张三确实是张三,这就是 authentication。

【授权】再一 check 用户张三是个版主,所以有权限加精删别人帖,这就是 authorization 。

2. 快速入门

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 Map
filterChainDefinitionMap() { 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; }}
  • 一共有三个 Bean 的配置,我们逐个来看看

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;    }
  • 在该方法里,我们添加了「admin/admin」和「normal/normal」两个用户,分别对应 ADMIN 和 NORMAL 角色。

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 Map
filterChainDefinitionMap() { 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; }
  • /test/echo :我们设置为 anon ,允许匿名访问。
  • /test/admin 和 /test/normal :我们设置为 roles[…] ,需要指定角色的用户可以访问。其中 …
    处为需要添加的角色名。
  • /logout :我们设置为 logout ,实现退出操作。 /** :剩余的 URL ,我们设置为 authc
    ,需要登录的用户才可以访问。同时,对于 loginUrl 需要执行登录相关的拦截。

另外,这里在补充一点,请求在 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() { /**省略代码**/ }}
  • 一共有 4 个接口,我们逐个来看看。

GET /login 地址,跳转登录页面。代码如下:

@GetMapping("/login")        public String loginPage() {            return "login";        }
  • 返回 resources/static/login.html 静态页面。代码如下:
    
登录页面
用户名:
密码:
  • 一个简单的登录的表单,POST 提交登录请求到 /login 地址上。

2.3.2 登录请求

对于登录请求,会被我们配置的 Shiro FormAuthenticationFilter 过滤器进行拦截,进行用户的身份认证。整个过程如下:

  • FormAuthenticationFilter 解析请求的 username、password 参数,创建
    UsernamePasswordToken 对象。
  • 然后,调用 SecurityManager 的 #login(Subject subject, AuthenticationToken
    authenticationToken) 方法,执行登录操作,进行“身份验证”(认证)。
  • 在这内部中,调用 Realm 的 #getAuthenticationInfo(AuthenticationToken token)
    方法,进行认证。此时,根据认证的是否成功,会有不同的处理: 如果认证通过,则 FormAuthenticationFilter
    会将请求重定向到 GET loginSuccess 地址上。
  • 【重要】如果认证失败,则会将认证失败的原因设置到请求的 attributes 中,后续该请求会继续请求到 POST login
    地址上。这样,在 POST loginUrl 地址上,我们可以从 attributes 中获取到失败的原因,提示给用户。

所以,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";        }
  • <1> 处,对于已经登录成功的用户,如果我们再次请求 POST loginUrl
    地址,依然会直接跳转到该地址上。此处,我们是提供用户已经的登录。可能有部分胖友会希望重新进行一次登录的逻辑,那么就需要重写
    FormAuthenticationFilter 过滤器。
  • <2> 处,从请求的 attributes中,获取FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME对应的值,即登录失败的原因。从代码中,我们可以看出,失败原因为异常的全类名,我们需要进行翻译成人类可读的提示。

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";    }}
  • 对于 /test/demo 接口,直接访问,无需登录。
  • 对于 /test/home 接口,无法直接访问,需要进行登录。
  • 对于 /test/admin 接口,需要登录「admin/admin」用户,因为需要 ADMIN 角色。
  • 对于 /test/normal 接口,需要登录「user/user」用户,因为需要 USER 角色。

2.5 Application

@SpringBootApplicationpublic class ShiroApplication {	public static void main(String[] args) {		SpringApplication.run(ShiroApplication.class, args);	}}

至此,我们已经完成了 Shiro 的入门。美滋滋,大家可以自己多多测试一下。

转载地址:http://gmmwb.baihongyu.com/

你可能感兴趣的文章
关于“团队建设”的反思
查看>>
利用jekyll在github中搭建博客
查看>>
Windows7中IIS简单安装与配置(详细图解)
查看>>
linux基本命令
查看>>
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
强引用 软引用 弱引用 虚引用
查看>>
数据类型 java转换
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>
mybatis 根据 数据库表 自动生成 实体
查看>>
win10将IE11兼容ie10
查看>>
checkbox设置字体颜色
查看>>
第一篇 HelloWorld.java重新学起
查看>>
ORACLE表空间扩张
查看>>
orcal 循环执行sql
查看>>
web.xml配置监听器,加载数据库信息配置文件ServletContextListener
查看>>
结构型模式之桥接模式(Bridge)
查看>>
行为型模式之状态模式(State)
查看>>
行为型模式之策略模式(Strategy)
查看>>
行为型模式之模板方法模式(TemplateMethod)
查看>>
行为型模式之访问者模式(Visitor)
查看>>