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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 基于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());
    }
}


目录
相关文章
|
3月前
|
安全 Java 数据库
第16课:Spring Boot中集成 Shiro
第16课:Spring Boot中集成 Shiro
636 0
|
7月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
369 0
|
7月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
272 0
|
6月前
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,日期时间格式化是前后端交互的常见痛点。本文详细解析了 **@DateTimeFormat** 和 **@JsonFormat** 两个注解的用法,分别用于将前端传入的字符串解析为 Java 时间对象,以及将时间对象序列化为指定格式返回给前端。通过完整示例代码,展示了从数据接收、业务处理到结果返回的全流程,并总结了解决时区问题和全局配置的最佳实践,助你高效处理日期时间需求。
768 0
|
6月前
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
190 0
|
4月前
|
缓存 安全 Java
Shiro简介及SpringBoot集成Shiro(狂神说视频简易版)
Shiro简介及SpringBoot集成Shiro(狂神说视频简易版)
361 6
|
6月前
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,处理前后端日期交互是一个常见问题。本文通过 **@DateTimeFormat** 和 **@JsonFormat** 两个注解,详细讲解了如何解析前端传来的日期字符串以及以指定格式返回日期数据。文章从实际案例出发,结合代码演示两者的使用场景与注意事项,解决解析失败、时区偏差等问题,并提供全局配置与局部注解的实践经验。帮助开发者高效应对日期时间格式化需求,提升开发效率。
1700 2
|
8月前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
1421 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
9月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
580 11
|
10月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
545 5

热门文章

最新文章