任何一个企业级零碎,权限必不可少
早年写的对于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: 5050spring: # 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 */@Configurationpublic 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 */@Componentpublic 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的数据库。