基于SpringBoot的Shiro实践应用开发总结(一)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 基于SpringBoot的Shiro实践应用开发总结

简介

Apache Shiro 是 Java 的一个安全(权限)框架相对于Spring Security更加轻量

Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。

• Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、 缓存等

 

功能简介

Authentication身份认证/登录,验证用户是不是拥有相应的身份;

Authorization授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

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

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

Web SupportWeb 支持,可以非常容易的集成到Web 环境;

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

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

• Testing:提供测试支持;

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

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

 

Shiro 架构

Shiro外部来看

从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作

 

Subject应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。 Subject 代表了当前“用户” , 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色

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

Shiro内部来看

• Subject:任何可以与应用交互的“用户”;

• SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。

Authenticator负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authorizer授权器、 即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能

Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm;

SessionManager管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web环境,也可以用在如普通的 JavaSE 环境

CacheManager缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能

Cryptography密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

 

Shiro工作原理

ShiroFilterFactoryBean的静态内部类SpringShiroFilter是Spring全局的过滤器,面注册了属于shiro自己的内置过滤器。Shiro内置了几个过滤器无需手动配置,我们也可以自定义过滤器他们都会在ShiroFilterFactoryBean初始化时加载到filters里。

以下为shior默认过滤器

SpringShiroFilter的工作流程

每个realm都有自己的数据缓存cache,二次调用直接从缓存中取数据

 

Shiro的基础知识

过滤器访问规则

• [urls] 部分的配置,其格式是: “ url=拦截器[参数],拦截器[参数]”;

• 如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会执行其配置的拦截器。

• anon(anonymous) 拦截器表示匿名访问(即不需要登录即可访问)

• authc (authentication)拦截器表示需要身份认证通过后才能访问

URL 模式使用 Ant 风格模式

• Ant 路径通配符支持 ?、 *、 **,注意通配符匹配不包括目录分隔符“ /”:

– ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;

– *:匹配零个或多个字符串,如 /admin 将匹配 /admin、/admin123,但不匹配 /admin/1;

– **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/

URL 匹配顺序

• URL 权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的 url 模式对应的拦截器链。

• 如:

– /bb/**=filter1

– /bb/aa=filter2

– /**=filter3

– 如果请求的url是“ /bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。

权限注解

• @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即 Subject. isAuthenticated() 返回 true

• @RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。

• @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。

• @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):表示当前 Subject 需要角色 admin 和user

• @RequiresPermissions (value={“ user:a”, “ user:b”},logical= Logical.OR):表示当前 Subject 需要权限 user:a 或user:b

会话

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境

都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、 SSO 单点登录的支持等特性。

会话监听器

会话监听器用于监听会话创建、过期及停止事件

项目实战

需引入的包

  <!-- shiro  -->
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
  </dependency>
  <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
     <version>1.4.0</version>
  </dependency>
  <dependency>
     <groupId>org.crazycake</groupId>
     <artifactId>shiro-redis</artifactId>
     <version>3.1.0</version>
  </dependency>
  <!--集成jwt实现token认证-->
  <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.2.0</version>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
         <exclusions>
             <exclusion>
                  <groupId>redis.clients</groupId>
                  <artifactId>jedis</artifactId>
             </exclusion>
         </exclusions>
     <version>2.3.1.RELEASE</version>
  </dependency>
  <dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
     <version>2.9.0</version>
  </dependency>

ShiroFilterFactoryBean的配置

Shiro通过Map集合组成了一个拦截器链 ,自顶向下过滤,一旦匹配,则不再执行下面的过滤。如果下面的定义与上面冲突,那按照了谁先定义谁说了算。PermissionsAuthorizationFilter继承自AuthorizationFilter所以也可以校验用户用户是否登入

  @Bean
    public ShiroFilterFactoryBean factory(SecurityManager securityManager, JdbcTemplate jdbcTemplate) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        factoryBean.setLoginUrl("/litigation/system/not/logged");
        factoryBean.setUnauthorizedUrl("/litigation/system/unauthorized");
        // 配置不会被拦截的链接
        List<String> urls = getIgnoredUrlsProperties().getUrls();
        for (String url : urls) {
            filterRuleMap.put(url, "anon");
        }
        List<Map<String, Object>> menus = jdbcTemplate.queryForList("select *  from menu");
        for (Map<String, Object> menu : menus) {
            Object url = menu.get("url");
            Object perms = menu.get("perms");
            if (url != null && perms != null) {
                filterRuleMap.put(url.toString(), "perms[" + perms.toString() + "]");
            }
        }
        filterRuleMap.put("/litigation/**","authc");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/litigation/system")
@Slf4j
public class SystemController {
    @RequestMapping(value = "/not/logged")
    public String notLogged(){
        throw new ServerException(ErrorCode.NO_LOGIN);
    }
    @RequestMapping(value = "/unauthorized")
    public String unauthorized(){
        throw new ServerException(ErrorCode.NO_PERMITTED);
    }
}

 

Menu表的数据

配置无需过滤的路径

ignored.urls[0]=/
ignored.urls[1]=/v2/api-docs
ignored.urls[2]=/swagger**/**
ignored.urls[3]=/swagger**/**/**
ignored.urls[4]=/webjars/**
ignored.urls[5]=/v2/**
ignored.urls[6]=/litigation/system/login
ignored.urls[7]=/litigation/system/not/logged
ignored.urls[8]=/litigation/system/unauthorized
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
 * @author lzhcode
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "ignored")
public class IgnoredUrlsProperties {
    private List<String> urls = new ArrayList<>();
}

Relm的登入和接口权限判断

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.*;
@Slf4j
public abstract class AbstractUserRealm extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;
    //获取用户角色的权限信息
    public abstract UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo);
    /**
     * 获取登入用户的所有角色和权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.getPrimaryPrincipal();
        String currentLoginName = user.getMobile();
        Set<String> userRoles = new HashSet<>();
        Set<String> userPermissions = new HashSet<>();
        //从数据库中获取当前登录用户的详细信息
        Map param = new HashMap<>();
        param.put("mobile",currentLoginName);
        List<User> users = userMapper.selectListSelective(param);
        if (!CollectionUtils.isEmpty(users)) {
            User  userInfo = users.get(0);
            UserRolesAndPermissions roleContainer = doGetRoleAuthorizationInfo(userInfo);
            userRoles.addAll(roleContainer.getUserRoles());
            userPermissions.addAll(roleContainer.getUserPermissions());
        } else {
            throw new AuthorizationException();
        }
        //为当前用户设置角色和权限
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(userRoles);
        authorizationInfo.addStringPermissions(userPermissions);
        return authorizationInfo;
    }
    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken对象用来存放提交的登录信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //查出是否有此用户
        Map param = new HashMap<>();
        param.put("mobile",token.getUsername());
        param.put("status",StatusEnum.NORMAL.getCode());
        List<User> users = userMapper.selectListSelective(param);
        if (!CollectionUtils.isEmpty(users)) {
            //防止重复登入
            SessionsSecurityManager securityManager = (SessionsSecurityManager) SecurityUtils.getSecurityManager();
            DefaultSessionManager sessionManager = (DefaultSessionManager) securityManager.getSessionManager();
            Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();//获取当前已登录的用户session列表
            for (Session session : sessions) {
                //清除该用户以前登录时保存的session
                //如果和当前session是同一个session,则不剔除
                if (SecurityUtils.getSubject().getSession().getId().equals(session.getId()))
                    break;
                User user = (User) (session.getAttribute("user"));
                if (user != null) {
                    String mobile = user.getMobile();
                    if (token.getUsername().equals(mobile)) {
                        log.info(mobile + "已登录,剔除中...");
                        sessionManager.getSessionDAO().delete(session);
                    }
                }
            }
            User  user = users.get(0);
            // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
          SimpleAuthenticationInfo  authcInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
          return authcInfo;
        }
        return null;
    }
    protected class UserRolesAndPermissions {
        Set<String> userRoles;
        Set<String> userPermissions;
        public UserRolesAndPermissions(Set<String> userRoles, Set<String> userPermissions) {
            this.userRoles = userRoles;
            this.userPermissions = userPermissions;
        }
        public Set<String> getUserRoles() {
            return userRoles;
        }
        public Set<String> getUserPermissions() {
            return userPermissions;
        }
    }
    @PostConstruct
    public void initCredentialsMatcher() {
      //该句作用是重写shiro的密码验
      setCredentialsMatcher(new CustomCredentialsMatcher());
    }
}


相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
161 0
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
166 2
|
2月前
|
安全 Java Apache
SpringBoot+Shiro(一)
SpringBoot+Shiro(一)
|
3月前
|
Java 大数据 分布式数据库
Spring Boot 与 HBase 的完美融合:探索高效大数据应用开发的新途径
【8月更文挑战第29天】Spring Boot是一款广受好评的微服务框架,以其便捷的开发体验著称。HBase则是一个高性能的大数据分布式数据库系统。结合两者,可极大简化HBase应用开发。本文将对比传统方式与Spring Boot集成HBase的区别,展示如何在Spring Boot中优雅实现HBase功能,并提供示例代码。从依赖管理、连接配置、表操作到数据访问,Spring Boot均能显著减少工作量,提升代码可读性和可维护性,使开发者更专注业务逻辑。
237 1
|
1月前
|
安全 Java 数据库
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
这篇文章是关于Apache Shiro权限管理框架的详细学习指南,涵盖了Shiro的基本概念、认证与授权流程,并通过Spring Boot测试模块演示了Shiro在单应用环境下的使用,包括与IniRealm、JdbcRealm的集成以及自定义Realm的实现。
44 3
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
|
25天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
58 2
|
1月前
|
Java API Apache
Springboot+shiro,完整教程,带你学会shiro
这篇文章提供了一个完整的Apache Shiro与Spring Boot结合使用的教程,包括Shiro的配置、使用以及在非Web和Web环境中进行身份验证和授权的示例。
66 2
Springboot+shiro,完整教程,带你学会shiro
|
1月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
314 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
27天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
25 2
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
30 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。

热门文章

最新文章

下一篇
无影云桌面