乐趣区

关于springboot:SpringBoot集成Shiro

任何一个企业级零碎,权限必不可少

早年写的对于 shiro(基本上是基于 SSM 框架 (即 Spring+SpringMVC+MyBatis) 文章如下 (仅供参考):
shiro 实战系列 \(一 \) 之入门实战
Spring\(二 \) 之入门示例
shiro 实战系列 \(二 \) 之入门实战续
shiro 实战系列 \(三 \) 之架构
shiro 实战系列 \(四 \) 之配置
shiro 实战系列 \(五 \) 之 Authentication\(身份验证 \)
shiro 实战系列 \(六 \)之 Authorization\(受权 \)
shiro 实战系列 \(七 \)之 Realm
shiro 实战系列 \(八 \)之平安管理器
shiro 实战系列 \(九 \) 之 Web
shiro 实战系列 \(十 \)之 Subject
shiro 实战系列 \(十一 \)之 Caching
shiro 实战系列 \(十二 \)之罕用专业术语
shiro 实战系列 \(十三 \) 之单元测试
shiro 实战系列 \(十四 \) 之配置
shiro 实战系列 \(十五 \) 之 Spring 集成 Shiro

下面一共十五篇文章是早年在守业公司做相干的技术调研整顿而成的,代码例子较少,偏理论性比拟强,所以本篇文章不再赘述一些理论性内容,接下来开始进入实战。

一、导入 Maven 依赖

这里列举的是子模块 pom.xml

<properties>
    <java.version>1.8</java.version>
    <druid-spring-boot-starter.version>1.1.13</druid-spring-boot-starter.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid-spring-boot-starter.version}</version>
    </dependency>
    <!-- SpringBoot Web -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- AOP 依赖, 肯定要加, 否则权限拦挡验证不失效 -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Mysql Connector -->
 <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!-- Redis -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- Shiro 外围依赖 -->
 <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- Shiro-redis 插件 -->
 <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!-- StringUtilS 工具 -->
 <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
    </dependency>
    <!-- json 转换工具 -->
 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

父 pom.xml(次要针对 SpringBoot 版本):

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/>
    </parent>

版本肯定要对,否则会有各种奇葩问题。

例如 (如果版本不对会呈现这样的问题, 启动失常,在申请登录接口就会报这样的谬误):
错误信息:

java.lang.NoSuchMethodError: redis.clients.jedis.ScanResult.getStringCursor()...

二、配置(application.yml)

server:
  port: 5050
spring:
  # Redis 数据源
 redis:
    host: localhost
    port: 6379
 timeout: 6000
    password: 123456
    jedis:
      pool:
        max-active: 1000 # 连接池最大连接数(应用负值示意没有限度)max-wait: -1      # 连接池最大阻塞等待时间(应用负值示意没有限度)max-idle: 10 # 连接池中的最大闲暇连贯
 min-idle: 5 # 连接池中的最小闲暇连贯
 # 配置数据源 datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/wordpress?useUnicode=true&characterEncoding=utf-8&serverTimeZone=GMT
    username: root
    password: 1234
    type: com.alibaba.druid.pool.DruidDataSource
# mybatis-plus 相干配置
mybatis-plus:
  # xml 扫描,多个目录用逗号或者分号分隔(通知 Mapper 所对应的 XML 文件地位)mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默认值, 能够不设置
 global-config:
    db-config:
      #主键类型 AUTO:"数据库 ID 自增" INPUT:"用户输出 ID",ID_WORKER:"全局惟一 ID (数字类型惟一 ID)", UUID:"全局惟一 ID UUID";
 id-type: auto
 #字段策略 IGNORED:"疏忽判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"
 field-strategy: NOT_EMPTY
      #数据库类型
 db-type: MYSQL
  configuration:
    # 是否开启主动驼峰命名规定映射: 从数据库列名到 Java 属性驼峰命名的相似映射
 map-underscore-to-camel-case: true
 # 如果查问后果中蕴含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
 call-setters-on-nulls: true
 # 这个配置会将执行的 sql 打印进去,在开发或测试的时候能够用
 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三、编写 Shiro 外围配置类

外围配置类特地留神的是接口放行,否则拜访接口会呈现 404。

package com.blog.tutorial07.shiro.config;
import com.blog.tutorial07.shiro.shiro.ShiroRealm;
import com.blog.tutorial07.shiro.shiro.ShiroSessionIdGenerator;
import com.blog.tutorial07.shiro.shiro.ShiroSessionManager;
import com.blog.tutorial07.shiro.utils.SHA256Util;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
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 java.util.LinkedHashMap;
import java.util.Map;
/**
 * @Description Shiro 配置类
 * @Author youcong
 */@Configuration
public class ShiroConfig {
    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";
    private final int EXPIRE = 1800;
    //Redis 配置
 @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;
    /**
 * 开启 Shiro-aop 注解反对 * @Attention 应用代理形式所以须要开启代码反对
 * @Author youcong
 */ @Bean
 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
 * Shiro 根底配置 * @Author youcong
 */ @Bean
 public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 留神过滤器配置程序不能颠倒
 // 配置过滤: 不会被拦挡的链接 filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/**", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        // 配置 shiro 默认登录界面地址,前后端拆散中登录界面跳转应由前端路由管制,后盾仅返回 json 数据
 shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
 * 平安管理器 * @Author youcong
 */ @Bean
 public SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义 Ssession 治理
 securityManager.setSessionManager(sessionManager());
        // 自定义 Cache 实现
 securityManager.setCacheManager(cacheManager());
        // 自定义 Realm 验证
 securityManager.setRealm(shiroRealm());
        return securityManager;
    }
    /**
 * 身份验证器 * @Author youcong
 */ @Bean
 public ShiroRealm shiroRealm() {ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }
    /**
 * 凭证匹配器 * 将明码校验交给 Shiro 的 SimpleAuthenticationInfo 进行解决, 在这里做匹配配置 * @Author youcong
 */ @Bean
 public HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法: 这里应用 SHA256 算法;
 shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
        // 散列的次数,比方散列两次,相当于 md5(md5(""));
 shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
        return shaCredentialsMatcher;
    }
    /**
 * 配置 Redis 管理器 * @Attention 应用的是 shiro-redis 开源插件
 * @Author youcong
 */ @Bean
 public RedisManager redisManager() {RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }
    /**
 * 配置 Cache 管理器 * 用于往 Redis 存储权限和角色标识 * @Attention 应用的是 shiro-redis 开源插件
 * @Author youcong
 */ @Bean
 public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY);
        // 配置缓存的话要求放在 session 外面的实体类必须有个 id 标识
 redisCacheManager.setPrincipalIdFieldName("id");
        return redisCacheManager;
    }
    /**
 * SessionID 生成器 * @Author youcong
 */ @Bean
 public ShiroSessionIdGenerator sessionIdGenerator(){return new ShiroSessionIdGenerator();
    }
    /**
 * 配置 RedisSessionDAO * @Attention 应用的是 shiro-redis 开源插件
 * @Author youcong
 */ @Bean
 public RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);
        redisSessionDAO.setExpire(EXPIRE);
        return redisSessionDAO;
    }
    /**
 * 配置 Session 管理器 * @Author youcong
 */ @Bean
 public SessionManager sessionManager() {ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        return shiroSessionManager;
    }
}

四、编写外围配置类中波及的相干类

ShiroRealm.java

package com.blog.tutorial07.shiro.shiro;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.blog.tutorial07.shiro.entity.Usermeta;
import com.blog.tutorial07.shiro.entity.Users;
import com.blog.tutorial07.shiro.service.UsermetaService;
import com.blog.tutorial07.shiro.service.UsersService;
import com.blog.tutorial07.shiro.utils.ShiroUtils;
import org.apache.shiro.authc.*;
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.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * @Description Shiro 权限匹配和账号密码匹配
 * @Author Sans
 * @CreateTime 2019/6/15 11:27
 */public class ShiroRealm extends AuthorizingRealm {
    @Autowired
 private UsersService sysUserService;
    @Autowired
 private UsermetaService sysRoleService;
    /**
 * 受权权限 * 用户进行权限验证时候 Shiro 会去缓存中找, 如果查不到数据, 会执行这个办法去查权限, 并放入缓存中 * * @Author Sans
 * @CreateTime 2019/6/12 11:44
 */ @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取用户 ID
 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Users user = (Users) principalCollection.getPrimaryPrincipal();
        Long userId = user.getId();
        // 这里能够进行受权和解决
 Set<String> rolesSet = new HashSet<>();
        QueryWrapper<Usermeta> roleWrapper = new QueryWrapper<>();
        roleWrapper.eq("user_id", user.getId());
        roleWrapper.eq("meta_key", "wp_user_level");
        List<Usermeta> roleList = sysRoleService.list(roleWrapper);
        for (Usermeta role : roleList) {rolesSet.add(role.getMetaValue());
        }
        authorizationInfo.setRoles(rolesSet);
        return authorizationInfo;
    }
    /**
 * 身份认证 * * @Author Sans
 * @CreateTime 2019/6/12 12:36
 */ @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户的输出的账号.
 String username = (String) authenticationToken.getPrincipal();
        // 通过 username 从数据库中查找 User 对象,如果找到进行验证
 // 理论我的项目中, 这里能够依据理论状况做缓存, 如果不做,Shiro 本人也是有工夫距离机制,2 分钟内不会反复执行该办法 QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("user_login", username);
        Users user = sysUserService.getOne(wrapper);
        // 判断账号是否存在
 if (user == null) {throw new AuthenticationException();
        }
        // 判断账号是否被解冻
 if (user.getUserStatus() == null || user.getUserStatus().equals("1")) {throw new LockedAccountException();
        }
        // 进行验证
 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,                                  // 用户名
 user.getUserPass(),                    // 明码
 ByteSource.Util.bytes(user.getUserActivationKey()), // 设置盐值
 getName());
        // 验证胜利开始踢人(革除缓存和 Session)
 ShiroUtils.deleteCache(username, true);
        return authenticationInfo;
    }
}

ShiroSessionIdGenerator.java

package com.blog.tutorial07.shiro.shiro;
import com.blog.tutorial07.shiro.constant.RedisConstant;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import java.io.Serializable;
/**
 * @Description 自定义 SessionId 生成器
 * @Author youcong
 */public class ShiroSessionIdGenerator implements SessionIdGenerator {
    @Override
 public Serializable generateId(Session session) {Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
        return String.format(RedisConstant.REDIS_PREFIX_LOGIN, sessionId);
    }
}

ShiroSessionManager.java

package com.blog.tutorial07.shiro.shiro;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
 * @Description 自定义获取 Token
 * @Author youcong
 */public class ShiroSessionManager extends DefaultWebSessionManager {
    // 定义常量
 private static final String AUTHORIZATION = "Authorization";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    // 重写结构器
 public ShiroSessionManager() {super();
        this.setDeleteInvalidSessions(true);
    }
    /**
 * 重写办法实现从申请头获取 Token 便于接口对立 * 每次申请进来,Shiro 会去从申请头找 Authorization 这个 key 对应的 Value(Token) * @Author youcong
 */ @Override
 public Serializable getSessionId(ServletRequest request, ServletResponse response) {String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        // 如果申请头中存在 token 则从申请头中获取 token
 if (!StringUtils.isEmpty(token)) {request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return token;
        } else {
            // 这里禁用掉 Cookie 获取形式
 // 按默认规定从 Cookie 取 Token // return super.getSessionId(request, response); return null;
        }
    }
}

五、编写相干工具类

SHA256Util.java

package com.blog.tutorial07.shiro.utils;
import org.apache.shiro.crypto.hash.SimpleHash;
/**
 * @Description Sha-256 加密工具
 * @Author youcong
 */public class SHA256Util {
    /**  公有结构器 **/
 private SHA256Util(){};
    /**  加密算法 **/
 public final static String HASH_ALGORITHM_NAME = "SHA-256";
    /**  循环次数 **/
 public final static int HASH_ITERATIONS = 15;
    /**  执行加密 - 采纳 SHA256 和盐值加密 **/
 public static String sha256(String password, String salt) {return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();}
}

ShiroUtils.java

package com.blog.tutorial07.shiro.utils;
import com.blog.tutorial07.shiro.entity.Users;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.LogoutAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisSessionDAO;
import java.util.Collection;
import java.util.Objects;
/**
 * @Description Shiro 工具类
 * @Author youcong
 */public class ShiroUtils {
    /**
 * 公有结构器 **/ private ShiroUtils() {}
    private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
    /**
 * 获取以后用户 Session * * @Author youcong
 * @Return SysUserEntity 用户信息
 */ public static Session getSession() {return SecurityUtils.getSubject().getSession();}
    /**
 * 用户登出 * * @Author youcong
 */ public static void logout() {SecurityUtils.getSubject().logout();}
    /**
 * 获取以后用户信息 * * @Author youcong
 * @Return SysUserEntity 用户信息
 */ public static Users getUserInfo() {return (Users) SecurityUtils.getSubject().getPrincipal();
    }
    /**
 * 删除用户缓存信息 * * @Author youcong
 * @Param username  用户名称
 * @Param isRemoveSession 是否删除 Session
 * @Return void
 */ public static void deleteCache(String username, boolean isRemoveSession) {
        // 从缓存中获取 Session
 Session session = null;
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        Users sysUserEntity;
        Object attribute = null;
        for (Session sessionInfo : sessions) {
            // 遍历 Session, 找到该用户名称对应的 Session
 attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {continue;}
            sysUserEntity = (Users) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (sysUserEntity == null) {continue;}
            if (Objects.equals(sysUserEntity.getUserLogin(), username)) {
                session = sessionInfo;
                break;
            }
        }
        if (session == null || attribute == null) {return;}
        // 删除 session
 if (isRemoveSession) {redisSessionDAO.delete(session);
        }
        // 删除 Cache,在拜访受限接口时会从新受权
 DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        Authenticator authc = securityManager.getAuthenticator();
        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
    }
}

SpringUtil.java

package com.blog.tutorial07.shiro.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @Description Spring 上下文工具类
 * @Author youcong
 */@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext context;
    /**
 * Spring 在 bean 初始化后会判断是不是 ApplicationContextAware 的子类 * 如果该类是,setApplicationContext()办法, 会将容器中 ApplicationContext 作为参数传入进去 * @Author youcong
 */ @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}
    /**
 * 通过 Name 返回指定的 Bean * @Author youcong
 */ public static <T> T getBean(Class<T> beanClass) {return context.getBean(beanClass);
    }
}

六、编写 Controller

package com.blog.tutorial07.shiro.controller;
import com.blog.tutorial07.shiro.entity.Users;
import com.blog.tutorial07.shiro.service.UsersService;
import com.blog.tutorial07.shiro.utils.ShiroUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @Autowired
 private RedisTemplate redisTemplate;
    /**
 * 登录 * * @Author youcong
 */ @PostMapping("/login")
    public Map<String, Object> login(@RequestParam String username, @RequestParam String password) {Map<String, Object> map = new HashMap<>();
        // 进行身份验证
 try {
            // 验证身份和登陆
 Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // 进行登录操作
 subject.login(token);
        } catch (IncorrectCredentialsException e) {map.put("code", 500);
            map.put("msg", "用户不存在或者明码谬误");
            return map;
        } catch (LockedAccountException e) {map.put("code", 500);
            map.put("msg", "登录失败,该用户已被解冻");
            return map;
        } catch (AuthenticationException e) {map.put("code", 500);
            map.put("msg", "该用户不存在");
            return map;
        } catch (Exception e) {map.put("code", 500);
            map.put("msg", "未知异样");
            return map;
        }
        map.put("code", 0);
        map.put("msg", "登录胜利");
        map.put("token", ShiroUtils.getSession().getId().toString());
        return map;
    }
    /**
 * 未登录 * * @Author youcong
 */ @RequestMapping("/unauth")
    public Map<String, Object> unauth() {Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("msg", "未登录");
        return map;
    }
    @PostMapping("/list")
    @RequiresRoles("1")
    public String list() {System.out.println("list:" + redisTemplate.opsForValue().get("list"));
        if (StringUtils.isEmpty(redisTemplate.opsForValue().get("list"))) {redisTemplate.opsForValue().set("list", usersService.list(), 360, TimeUnit.MINUTES);
        }
        return redisTemplate.opsForValue().get("list").toString();}
}

七、测试(应用 PostMan)

1. 登录

查看 redis,如图:

2. 测试没有权限的接口

3. 赋予权限再次测试

八、总结

无论是 SpringSecurity 还是 Shiro,基本上整合十分类似,也很简略。
如果有敌人看完这篇文章还是不明确的话,能够拜访如下地址:
https://github.com/developers…
将我的项目克隆到本地运行。这个 git 仓库,sql 脚本什么的都有。

我本次用到的类基本上是基于这个的,只不过数据表不一样,我本次所应用的是 wordpress 的数据库。

退出移动版