Shiro简介与示例


Shiro简介与示例

什么是Shiro

Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、
加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应
用程序——从最小的移动应用程序到最大的网络和企业应用程序。

简而言之,Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。

在这里插入图片描述

Authentication(认证):用户身份识别,通常被称为用户“登录”

Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。

Session Management(会话管理):会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的。

Cryptography(加密):加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。

还有一些扩展的功能:
Web Support:Web支持,可以非常容易的集成到 web 环境。

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。

Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。

Testing:提供测试支持。

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

Shiro的认证过程

在这里插入图片描述
①Subject:主体,代表了当前“用户”。
这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等。所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager。我们可以把 Subject 认为是一个门面,SecurityManager 才是实际的执行者。

②SecurityManager:安全管理器。
即所有与安全有关的操作都会与 SecurityManager 交互,且它管理着所有 Subject。可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,我们可以把它看成 DispatcherServlet 前端控制器。

③Realm:域。
Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法,也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作。我们可以把 Realm 看成 DataSource,即安全数据源。

Shiro靠什么做认证与授权的?

Shiro可以利用HttpSession或者Redis存储用户的登陆凭证,以及角色或者身份信息。然后利用过滤器(Filter),对每个Http请求过滤,检查请求对应的HttpSession或者Redis中的认证与授权信息。如果用户没有登陆,或者权限不够,那么Shiro会向客户端返回错误信息。

也就是说,我们写用户登陆模块的时候,用户登陆成功之后,要调用Shiro保存登陆凭证。然后查询用户的角色和权限,让Shiro存储起来。将来不管哪个方法需要登陆访问,或者拥有特定的角色跟权限才能访问,我们在方法前设置注解即可,非常简单。

示例(源代码见GITHUB)

在这里插入图片描述

POM.XML文件中引入Shiro依赖

    <!-- thymel对shiro的扩展坐标 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

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

index.html

<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>test</h4>
<hr/>

<div shiro:hasPermission="user:add">
<a href="/add">用户添加</a>
</div>
<br/>
<div shiro:hasPermission="user:update">
<a href="/update">用户更新</a>
</div>
</body>
</html>
<div shiro:hasPermission="user:add"> </div>
在该div中的元素只有在用户有 user:add 资源权限时才会显示    

login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 style="align-content: center;">用户登录</h1>


<font style="color: red;" th:text="${msg}"></font>
<form action="/tologin">
    用户名:<input name="name" />  <br/>
    密码:<input name="password" type="password"/>  <br/>
    <input type="submit" value="登录"/>  <br/>

</form>
</body>
</html>
<font style="color: red;" th:text="${msg}"></font>
用来展示登录验证信息。从Controller 中拿值

controller:

package com.sl.spring.controller;

import java.lang.ProcessBuilder.Redirect;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {
private final String PREFIX = "user/";
    /**
     * 测试方法
     */
    @RequestMapping("/test")
    public String test(){
        return "index";
    }
    @RequestMapping("/add")
    public String add(){
        return PREFIX+"add";
    }
    @RequestMapping("/update")
    public String update(){
        return PREFIX+"update";
    }
    
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
    
    
    /**
     * 登录逻辑处理
     */
    @RequestMapping("/tologin")
    public String tologin(String name,String password,Model model){
        //使用Shiro编写认证操作
        Subject subject = SecurityUtils.getSubject();
        //封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        
        //执行登录方法
        try {
            subject.login(token);
        //没有捕获到异常就是登录成功    
            return "redirect:/test";//重定向到/test请求从而跳转到index.html
        } catch (UnknownAccountException e) {
//              登陆失败:用户名不存在
            model.addAttribute("msg", "用户名不存在!");
            return "login";//因为存放有数据,重定向会丢失数据,所以直接返回页面
            
        }catch (IncorrectCredentialsException e) {
//             登陆失败:密码错误
            model.addAttribute("msg", "密码错误!");
            return "login";
            
        }
    
    }
    
    
    @RequestMapping("/unAuth")
    public String unAuth(){
        return "unAuth";
    }
    
    
    
}
登录逻辑其实是根据是否抛出异常来判断是否成功,如果失败,则利用model把错
误信息放到request域中,然后login.html就能拿到该信息并展示给用户。

Shiro配置类所依赖的realm:

package com.sl.spring.realm;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

import com.sl.spring.entity.User;
import com.sl.spring.service.UserService;

public class UserRealm  extends AuthorizingRealm{
/*
 * 执行授权逻辑
 */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权逻辑");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                
                
        //获取当前登录的用户
        Subject subject = SecurityUtils.getSubject();    
        User user=(User) subject.getPrincipal();
        
        User dUser=service.findById(user.getId());
        System.out.println(dUser.toString());
        info.addStringPermission(dUser.getPerms());
                return info;
    }

    /*
     * 执行认证逻辑
     */
    @Autowired
    private UserService service;
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println(" 执行认证逻辑");
        
        //编写判断逻辑,来判断用户名和密码是否正确
        UsernamePasswordToken token2=(UsernamePasswordToken) token;//Controller传来的Token
        
        User user = service.findByName(token2.getUsername());
        
        
        
        //1、判断用户名
        if (user==null) {
            //用户名不存在
            return null;//Shiro底层会抛出UnknownAccountException
            
        }
        
        //2、判断密码
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }

}
UsernamePasswordToken token2=(UsernamePasswordToken) token;
这里的token就是前面controller用来封装用户数据的那个token

=========
//2、判断密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");

这里的user会传到授权逻辑的方法去。
======================================
User user=(User) subject.getPrincipal();
来获得上面的那个user


info.addStringPermission(dUser.getPerms());
从数据库中获得用户对应的资源权限,并赋给用户

配置类:

package com.sl.spring.config;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.sl.spring.realm.UserRealm;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
public class ShiroConfig {
/**
 * 创建Realm
 */
    @Bean(value = "getRealm")
    public  UserRealm getRealm() {
        
        return new UserRealm();
    }
    
    /**
     * 创建DefaultWebSecurityManager
     */
    
    @Bean(value = "security")
    public DefaultWebSecurityManager gDefaultWebSecurityManager(@Qualifier("getRealm") UserRealm realm) {
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(realm);
        return securityManager;
    }
    
    /**
     * 创建ShiroFilterFactoryBean
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("security")DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
    
        //设置安全管理器
        factoryBean.setSecurityManager(securityManager);
        
        //添加shiro的内置过滤器
        /**
         * Shiro内置过滤器,可以实现权限相关的拦截器
         *    常用的过滤器:
         *       anon: 无需认证(登录)可以访问
         *       authc: 必须认证才可以访问
         *       user: 如果使用rememberMe的功能可以直接访问
         *       perms: 该资源必须得到资源权限才可以访问
         *       role: 该资源必须得到角色权限才可以访问
         */
        Map<String, String> map=new LinkedHashMap<String, String>();
        //放行/tologin请求
        map.put("/tologin", "anon");
        
        
        //授权过滤器
        //注意,当前授权拦截后,Shiro会自动跳转到一个提示未授权的页面
        map.put("/add", "perms[user:add]");
        map.put("/update", "perms[user:update]");
        
        //要过滤什么请求
        map.put("/*", "authc");//这个一定要写最后,不然没办法执行授权逻辑(过滤链是有顺序的)
        
        //修改被拦截后的跳转页面
        factoryBean.setLoginUrl("/login");
        
        
        //设置未授权提示页面
        factoryBean.setUnauthorizedUrl("/unAuth");
        
        
        factoryBean.setFilterChainDefinitionMap(map);
        return factoryBean;
    }
    
    /**
     * 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
     */
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

}
Realm-->DefaultWebSecurityManager-->ShiroFilterFactoryBean
逐层依赖。
===========
map.put("/*", "authc");
一定要写最后,不然授权无法访问,过滤链是有顺序的
===================
map.put("/add", "perms[user:add]");/add请求增加一个资源授权限制

userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 该文件存放CRUD的sql语句 -->
<mapper namespace="com.sl.spring.mapper.UserMapper">
    
    <select id="findByName" parameterType="string" resultType="com.sl.spring.entity.User">
    SELECT     id, 
        NAME, 
        PASSWORD,
        perms
        FROM 
        user where name = #{name}
    </select>
    
    <select id="findById" parameterType="int" resultType="com.sl.spring.entity.User">
    SELECT     id, 
        NAME, 
        PASSWORD,
        perms
        FROM 
        user where id = #{id}
    </select>
</mapper>

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