玩命加载中 . . .

SpringBoot整合shiro


1 Shiro 三大核心组件

​ Shiro 有三大核心组件,即 Subject、SecurityManager 和 Realm。先来看一下它们之间的关系。

1.1 Subject 为认证主体

​ 包含 Principals 和 Credentials 两个信息。我们看下两者的具体含义。

​ Principals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。

​ Credentials:代表凭证。常见的有密码,数字证书等等。

​ 说白了,两者代表了需要认证的内容,最常见的便是用户名、密码了。比如用户登录时,通过 Shiro 进行身份认证,其中就包括主体认证。

1.2 SecurityManager 为安全管理员

​ 这是 Shiro 架构的核心,是 Shiro 内部所有原件的保护伞。项目中一般都会配置 SecurityManager,开发人员将大部分精力放在了 Subject 认证主体上,与 Subject 交 互背后的安全操作,则由 SecurityManager 来完成。

1.3 Realm 是一个域

​ 它是连接 Shiro 和具体应用的桥梁。当需要与安全数据交互时,比如用户账户、访问控制等,Shiro 将会在一个或多个 Realm 中查找。我们可以把 Realm 看作 DataSource,即安全数据源。一般,我们会自己定制 Realm,下文会详细说明。

2 Shiro 身份和权限认证

2.1 Shiro 身份认证

​ 我们分析下 Shiro 身份认证的过程,首先看一下官方给出的认证图。

​ Step1:应用程序代码调用 Subject.login(token) 方法后,传入代表最终用户身份的 AuthenticationToken 实例 Token。

​ Step2:将 Subject 实例委托给应用程序的 SecurityManager(Shiro 的安全管理)并开始实际的认证工作。这里开始了真正的认证工作。

​ Step3、4、5:SecurityManager 根据具体的 Realm 进行安全认证。从图中可以看出,Realm 可进行自定义(Custom Realm)。

2.2 Shiro 权限认证

​ 权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源。在权限认证中,最核心的三个要素是:权限、角色和用户

权限(Permission):即操作资源的权利,比如访问某个页面,以及对某个模块的数据进行添加、修改、删除、查看操作的权利。
角色(Role):指的是用户担任的角色,一个角色可以有多个权限。
用户(User):在 Shiro 中,代表访问系统的用户,即上面提到的 Subject 认证主体。

​ 一个用户可以有多个角色,而不同的角色可以有不同的权限,也可有相同的权限。比如说现在有三个角色,1 是普通角色,2 也是普通角色,3 是管理员,角色 1 只能查看信息,角色 2 只能添加信息,管理员对两者皆有权限,而且还可以删除信息。

3 Spring Boot 集成 Shiro

3.1 依赖导入

​ Spring Boo 集成 Shiro 需要导入如下 starter 依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

3.2准备页面和跳转

@Controller
public class ViewController {

    /**
     * 跳转到login页面
     */
    @RequestMapping("toLogin")
    public String toLogin(){
        return "login";
    }

    /**
     * 登录校验
     * @param username 用户名
     * @param password 密码
     * @param model
     * @return
     */
    @RequestMapping("/login")
    public String Login(String username, String password, Model model){
        //认证主体
        Subject subject = SecurityUtils.getSubject();
        //把用户封装成一个token
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            return "index";
        }catch (UnknownAccountException e) {
            model.addAttribute("message","用户不存在");
            return "login";
        } catch (AuthenticationException e) {
            model.addAttribute("message","密码错误");
            return "login";
        }
    }
    
    /**
     * 不想准备页面,直接返回字符串
     */
    @RequestMapping("/vip/vip1")
    @ResponseBody
    public String toVip1(){
        return "已进入vip1";
    }
    @RequestMapping("/vip/vip2")
    @ResponseBody
    public String toVip2(){
        return "已进入vip2";
    }

}

​ 登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>title</title>
<body>
    <p th:text="${message}" style="color: red"></p>
    <form th:action="@{/login}">
        用户名:<input type="text" name="username"><br>
        密 码:<input type="text" name="password">
        <input type="submit" value="提交">
    </form>
</body>

​ index页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>title</title>
<body>
<h1>这是首页</h1>
<a th:href="@{/vip/vip1}">vip/vip1</a>
<a th:href="@{/vip/vip2}">vip/vip2</a>
</body>

3.3 自定义 Realm

​ 自定义 Realm 需要继承 AuthorizingRealm 类,该类封装了很多方法,且继承自 Realm 类。

​ 继承 AuthorizingRealm 类后,我们需要重写以下两个方法。

​ doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。

具体实现如下,相关注解请见代码注释:

public class UserRealm extends AuthorizingRealm {
    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //可以创建set集合同时添加多个权限
        Set<String> perms = new HashSet<String>();
        perms.add("user:vip1");
        //perms.add("user:vip2");
        //给用户添加权限
        info.setStringPermissions(perms);
        //info.setRoles();  给用户添加角色
        return info;
    }

    /**
     * 认证
     * @param token
     * @return SimpleAuthenticationInfo
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //可以根据用户名从数据库查询用户,为了方便这里伪造一个用户
        String user = "root";
        String pw = "password";
        //转换成登录时的token
        UsernamePasswordToken token1 = (UsernamePasswordToken) token;
        if (!token1.getUsername().equals(user)) {
            //用户名不正确,自动抛出UnknownAccountException
            return null;
        }
        //密码shiro帮我们验证,错误也会抛出异常
        return new SimpleAuthenticationInfo("",pw,"");
    }
}

​ 从上面两个方法中可以看出,验证身份时需先根据用户输入的用户名从数据库中查出对应的用户,这时还未涉及到密码,也就是说即使用户输入的密码不正确,照样可以查询出该用户。

​ 然后,将该用户的相关信息封装到 authcInfo 中并返回给 Shiro。接下来就该 Shiro 上场了,将封装的用户信息与用户的输入信息(用户名、密码)进行对比、校验(注意,这里对密码也要进行校验)。校验通过则允许用户登录,否则跳转到指定页面。

​ 同理,权限验证时,也需先根据用户名从数据库中获取其对应的角色和权限,将其封装到 authorizationInfo 并返回给 Shiro。

3.4 Shiro 配置

​ 自定义 Realm 写好了,接下来需要配置 Shiro。我们主要配置三个东西:自定义 Realm、安全管理器 SecurityManager 和 Shiro 过滤器。

​ 首先,配置自定义的 Realm,代码如下:

@Configuration
public class ShiroConfig {

    /**
     * 获取自定义的Realm
     * @return
     */
    public UserRealm getUserRealm(){
        return new UserRealm();
    }
}

​ 接着,配置安全管理器 SecurityManager:

@Configuration
public class ShiroConfig {

    /**
     * 注入安全管理器
     * @return securityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(getUserRealm());
        return securityManager;
    }
    
}

​ 配置 SecurityManager 时,需要将上面自定义 Realm 添加进来,这样 Shiro 才可访问该 Realm。

​ 最后,配置 Shiro 过滤器:

@Configuration
public class ShiroConfig {

    /**
     * 注入 Shiro 过滤器
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        /**
         * 常用的几个权限
         * anon 所有人都可以通过
         * authc 必须认证了才能通过
         * perms 拥有该资源权限才能通过
         * user 开启记住我功能可以通过
         * role 拥有该角色才能通过
         */
        Map<String,String> filter = new LinkedHashMap<>();
        filter.put("/","anon");
        //filter.put("/vip/**","authc");
        //访问vip1必须拥有vip1权限才能通过
        filter.put("/vip/vip1","perms[user:vip1]");
        filter.put("/vip/vip2","perms[user:vip2]");
        bean.setFilterChainDefinitionMap(filter);
        //设置登录页面
        bean.setLoginUrl("/toLogin");
        return bean;
    }
}

​ 配置 Shiro 过滤器时,我们引入了安全管理器。

​ Shiro 配置一环套一环,遵循从 Reaml 到 SecurityManager 再到 Filter 的过程。在过滤器中,我们需要定义一个 shiroFactoryBean,然后将 SecurityManager 引入其中,需要配置的内容主要有以下几项。

  • 默认登录的 URL———setLoginUrl()。
  • 认证成功之后要跳转的 URL——–setSuccessUrl()。
  • 需要拦截或者放行的 URL:这些都放在一个 Map 中——–setFilterChainDefinitionMap()。

文章作者: 糖人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 糖人 !
评论
  目录