Springboot+Shiro实战

Springboot+shiro 实战

1、Shiro概述(what)

​ Apache Shiro是一种功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理,可用于保护任何应用程序的安全- 从命令行应用程序,移动应用程序到最大的Web和企业应用程序。

1.1 功能概述

  • 验证用户来核实他们的身份
  • 对用户执行访问控制
    • 判断用户是否被分配一个特定的角色
    • 判定用户是否被允许做什么事
  • 任何环境下都可以使用session API,即使没有web或者EJB容器
  • 在身份验证,访问控制期间或者会话的声明周期,对事件做出反应
  • 聚集一个或者多个用户的安全数据的数据源,并作为一个单一的复合用户视图
  • 启动单点登录功能
  • 启用Remember me 服务。

1.2 Shiro和Spring security的区别

  • shiro

    shiro 相比较于spring security 在保持强大功能的同时,具有简单和灵活优势。

    1. 易于理解的Java security API 。
    2. 简单的身份认证(登录),支持多多种数据源(LDAP、JDBC、Kerberos、ActiveDirectory等)。
    3. 对角色的简单鉴权(访问控制),支持细粒度鉴权。
    4. 内置基于POJO的企业会话管理,适用于web和非web环境
    5. 异构客户端会话访问
    6. 非常简单的加密API
    7. 不和任何框架捆绑,可以独立运行
  • security

    1. 优点:
      1. 对oauth、openID有支持,shiro需要手动实现
      2. 权限粒度更细
    2. 缺点
      1. 必须和spring结合使用

1.3 shiro的功能模块

认证、授权、加密、会话管理、与web集成和会话管理、API简单、可以独立运行,功能如下:
在这里插入图片描述

2、Shiro工作原理(why)

2.1 shiro的架构理解

在这里插入图片描述

  • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
  • SecurityManager : 相 当 于 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中的 FilterDispatcher,是 Shiro 的核心;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者缓存实现等等,由用户提供。注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
  • SessionManager:会话管理器,如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Redis服务器);
  • SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把Session 放到 Memcached/Redis 中,可以实现自己的 Memcached/Redis SessionDAO;另外 SessionDAO中可以使用 Cache 进行缓存,以提高性能;
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本
    上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密

2.2 Realm 域

在这里插入图片描述

一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现),重写里面AuthenticationInfo 认证和doGetAuthorizationInfo授权方法。

2.3 应用程序使用shiro

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pchHc7NL-1587288125363)(图片/shiro-应用.png)]

2.4 认证流程

在这里插入图片描述

2.5 授权流程

在这里插入图片描述

2.3 shiro 拦截规则

在这里插入图片描述

3、应用场景(who\where\when)

3.1、特点

Apache Shiro旨在成为最全面,但也最容易使用的Java安全框架。以下是一些框架要点:
(1)在任何地方最容易理解的Java安全性API。类和接口的名称很直观并且很有意义。任何东西都是可插入的,但是所有东西都存在良好的默认值。
(2)支持跨一个或多个可插拔数据源(LDAP,JDBC,ActiveDirectory等)的身份验证(“登录”)。
(3)还可使用可插拔数据源,根据角色或细粒度权限执行授权(“访问控制”)。
(4)一流的缓存支持可增强应用程序性能。
(5)内置基于POJO的企业会话管理。可在Web和非Web环境中使用,也可在需要单点登录(SSO)或群集或分布式会话的任何环境中使用。
(6)异构客户端会话访问。您不再被迫仅使用httpSession或有状态会话Bean,它们常常不必要地将应用程序绑定到特定环境。现在,无论部署环境如何,Flash Applet,C#应用程序,Java Web Start和Web应用程序等都可以共享会话状态。
(7)简单单点登录(SSO)支持附带上述企业会话管理。如果会话是跨多个应用程序联合的,则用户的身份验证状态也可以共享。一次登录到任何应用程序,其他都可以识别该登录。
(8)使用最简单的可用加密API保护数据安全,从而为您提供强大的功能和简便性,而Java默认情况下不提供加密和哈希功能。
(9)一个非常健壮但配置低的Web框架,可以保护任何URL或资源,自动处理登录和注销,执行“记住我”服务等等。
(10)所需依赖项的数量极少。独立配置仅需要slf4j-api.jar和slf4j的绑定.jars之一。 Web配置还需要commons-beanutils-core.jar。可以在需要时添加基于功能的依赖项(Ehcache缓存,基于Quartz的会话验证,Spring依赖项注入等)。

4、实战(how)

4.1 依赖

 <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-core</artifactId>
     <version>1.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.2</version>
</dependency>
<!--开源组件:shiro redis管理shiro会话,自定义shiro会话
    升版本:AuthCachePrincipal就会找不到-->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.0.0</version>
</dependency>

4.2 配置文件

spring:
  # 应用配置
  application:
    name: shiro_springboot
  # 数据库连接池
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.35.80:3306/shiro_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: xxx
    password: xxx
  redis:
    host: 192.168.35.80
    port: 6379
    password: xxx
    database: 1

4.3 配置类


import com.sd.shiro.session.CustomerSessionManager;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;

import java.util.LinkedHashMap;

/**
 * @author 三多
 * @Time 2020/4/2
 */
@Configuration
public class ShiroConfig {
    /**
     * 配置shiro的过滤器工厂
     *      一组过滤器集合
     *      拦截页面跳转
     * 步骤:
     *      1.创建过滤器工厂
     *      2.设置安全管理器
     *      3.通用配置(跳转登录页面,未授权跳转的页面)
     *      4.设置过滤器集合
     * 问题:web.xml 没有配置shiro相关的拦截器
     * 解决问题:
     *      org.apache.shiro.UnavailableSecurityManagerException:
     *      No SecurityManager accessible to the calling code,
     *      either bound to the org.apache.shiro.util.
     *      ThreadContext or as a vm static singleton.
     *      This is an invalid application configuration.
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager ) {
        // 1.创建过滤器工厂
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 2.设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //3.通用配置(跳转登录页面,未授权跳转的页面)
        //跳转url
        shiroFilterFactoryBean.setLoginUrl("/authError?code=1");
        //未授权url
        shiroFilterFactoryBean.setUnauthorizedUrl("/authError?code=2");
        // 在 Shiro过滤器链上加入 JWTFilter
        /*LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filters);*/
        //4.设置过滤器集合,map集合,设置所有的过滤器有顺序:key:拦截的地址;value:过滤器类型
        LinkedHashMap<String, String> filterMap= new LinkedHashMap<>();
        //a.当前请求可以匿名访问
        //filterMap.put("/user/home","anon");
        //b.具有某种权限才可以访问,不具备,会跳转到UnauthorizedUrl地址
        //filterMap.put("/user/home","perms[user-home]");
        //c.使用过滤器过滤角色(可以使用注解方式)
        //filterMap.put("/user/home","roles[系统管理员]");
        filterMap.put("/user/login","anon");
        //filterMap.put("/user/find","anon");
        //当前请求地址必须认证后访问
        filterMap.put("/user/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建shiro安全管理器
     *      管理所有的realm
     * @return
     */
    @Bean
    public SecurityManager securityManager(CustomerRealm customerRealm) {
        //使用web默认的额缓存管理器,可以自定义使用外部的缓存管理器
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置 SecurityManager,并注入 shiroRealm
       securityManager.setRealm(customerRealm);
        //可选:设置自定义的会话管理器
        securityManager.setSessionManager(sessionManager());
       //可选:设置自定义的缓存管理器
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    /**
     * 创建realm
     * @return
     */
    @Bean
    public CustomerRealm shiroRealm() {
        // 配置 Realm
        return new CustomerRealm();
    }


    /**
     * 自定义Session会话
     *      1、redis的控制器、操作redis
     *      2、SessionDao
     *      3、会话管理器
     *      4、缓存管理器
     */
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;

    /***
     * 1、redis的控制器、操作redis
     * @return
     */
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        /*GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxIdle(100);
        poolConfig.setMaxTotal(100);
        poolConfig.setMinIdle(0);
        JedisPool jedisPool = new JedisPool(poolConfig,host,port);
        redisManager.setJedisPool(jedisPool);*/
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setDatabase(database);
        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * 2、SessionDao
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 3、会话管理器
     * @return
     */
    public DefaultWebSessionManager sessionManager(){
        CustomerSessionManager sessionManager = new CustomerSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return  sessionManager;
    }

    /**
     * 4、缓存管理器
     * @return
     */
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 开启对shiro注解的支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

4.4 自定义realm


import com.sd.shiro.domain.entity.Permission;
import com.sd.shiro.domain.entity.Role;
import com.sd.shiro.domain.entity.User;
import com.sd.shiro.service.IUserService;
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.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * @author 三多
 * @Time 2020/4/2
 */
//@Component("customerRealm")
public class CustomerRealm extends AuthorizingRealm {

    @Override
    public void setName(String name){
        super.setName("customerRealm");
    }

    @Autowired
    private IUserService userService;
    /**
     * 授权
     *      判断用户是否具有相应的权限
     *        1.先认证----安全数据
     *        2.在授权----根据安全数据获取用户相应的操作权限
     *    1.获取已认证的用户数据
     *    2.获取用户的权限(角色,权限)
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 1.获取已认证的用户数据
        User user = (User) principals.getPrimaryPrincipal();
        //2.获取用户的权限(角色,权限)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> roles = new HashSet<String>();
        Set<String> perms = new HashSet<String>();
        for (Role role : user.getRoles()) {
            //角色
            roles.add(role.getName());
            for (Permission perm : role.getPermissions()) {
                //权限
                perms.add(perm.getCode());
            }
        }
        info.setRoles(roles);
        info.setStringPermissions(perms);
        return info;
    }


    /**
     * 认证
     *        1.获取用户名密码
     *        2.根据用户名查询
     *        3.判断密码是否一致
     *        4.如果一致返回安全数据
     *        5.不一致,返回null,或者抛异常
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.获取用户名密码
        UsernamePasswordToken passwordToken = (UsernamePasswordToken)token;
        String username = passwordToken.getUsername();
        String password = new String(passwordToken.getPassword());
        // 2.根据用户名查询
        User user = userService.findByUsername(username);
        //3.判断密码是否一致
        if(Objects.nonNull(user) && user.getPassword().equals(password)){
             // 4.如果一致返回安全数据 用户数据,密码,realm域名称
            AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }
        // 5.不一致,返回null,或者抛异常
        return null;
    }
}

4.5 自定义session管理器


import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * 自定义  sessionManager
 *
 * @author 三多
 * @Time 2020/4/14
 */
public class CustomerSessionManager extends DefaultWebSessionManager {
    private static final String AUTHOR_HEADER = "Authorization";
    public static final String HEADER_SESSION_ID_SOURCE = "header";

    /**
     * 头信息中具有sessionID
     *      请求头:Authorization:sessionId
     *      指定sessionId的获取方式
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //获取请求头中Authorization数据。sessionId
        String id = WebUtils.toHttp(request).getHeader(AUTHOR_HEADER);
        if (StringUtils.isEmpty(id)) {
            //如果没有生成新的sessionId
            return super.getSessionId(request, response);
        } else {
            //请求头信息 :Bearer sessionId
            id = id.replace("Bearer ","");
            //返回SessionId
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    HEADER_SESSION_ID_SOURCE);
 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        }
    }
}

4.6 源码地址

springboot+shiro

5、参考

  1. Application Security With Apache Shiro

  2. 跟我学 Shiro

  3. shiro框架详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值