任何一個企業級系統,權限必不可少html
早年寫的關於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前端
上面一共十五篇文章是早年在創業公司作相關的技術調研整理而成的,代碼例子較少,偏理論性比較強,因此本篇文章再也不贅述一些理論性內容,接下來開始進入實戰。java
這裏列舉的是子模塊pom.xmlmysql
<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版本):react
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> </parent>
版本必定要對,不然會有各類奇葩問題。git
例如(若是版本不對會出現這樣的問題,啓動正常,在請求登陸接口就會報這樣的錯誤):
錯誤信息:github
java.lang.NoSuchMethodError: redis.clients.jedis.ScanResult.getStringCursor()...
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
核心配置類特別注意的是接口放行,不然訪問接口會出現404。web
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.javaredis
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); } }
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(); } }
查看redis,如圖:
不管是SpringSecurity仍是Shiro,基本上整合很是類似,也很簡單。
若是有朋友看完這篇文章仍是不明白的話,能夠訪問以下地址:
https://github.com/developers...
將項目克隆到本地運行。這個git倉庫,sql腳本什麼的都有。
我本次用到的類基本上是基於這個的,只不過數據表不同,我本次所使用的是wordpress的數據庫。