简介:踢出用户功能:就是限制一个账号登陆人数。

本文限定一个账号一个用户登陆,并且是挤掉前一个用户


目录

首先 pom

然后Shiro配置Bean  ShiroConfigBean

然后配置 ShiroRealm(百度翻译: Realm 领域)

然后sessiondao SessionDAO

然后 配置踢人(挤人)逻辑 KickoutSessionControlFilter

UnknownSessionException异常原因

service

@RestController


首先 pom

        <!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.2.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.2</version></dependency>

然后Shiro配置Bean  ShiroConfigBean


import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** @author liguanhua* @Date: 2019/12/17 11:21* @Description: Shiro配置Bean*/
@Configuration
public class ShiroConfigBean {private static final String NOT_FILTER_STR = "/login|/photoUpload|/img/|/loginOut|/api/login|/importExcel|/api/deviceGis/add" +"|/api/log/uploadLog|/upload|/api/app/update|/api/client/update|/api/device/deviceDataByMDCodeL|/land";private static List<String> NOTFILTER_ARRAY;static {NOTFILTER_ARRAY = Arrays.asList(NOT_FILTER_STR.split("\\|"));}@Beanpublic ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {System.out.println("ShiroConfiguration.shirFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//自定义拦截器 ---------------配置挤人功能呢---------------------- Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//        限制同一帐号同时在线的个数。filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);
//        ----------------------------------------------------// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 拦截器.Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 设置login URLshiroFilterFactoryBean.setLoginUrl("/land");for (String str : NOTFILTER_ARRAY) {shiroFilterFactoryBean.setLoginUrl(str);}// 设置不需要校验的 urlfor (String str : NOTFILTER_ARRAY) {filterChainDefinitionMap.put(str, "anon");}filterChainDefinitionMap.put("/Exception.class", "anon");// 我写的url一般都是xxx.action,根据你的情况自己修改filterChainDefinitionMap.put("/*", "authc");// 退出系统的过滤器filterChainDefinitionMap.put("/loginOut", "logout");// 最后一班都,固定格式//其他资源都需要认证  authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址 kickout 挤人功能配置filterChainDefinitionMap.put("/**", "kickout,authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/** 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码; )*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(1);// 散列的次数,比如散列两次,相当于md5(md5(""));return hashedCredentialsMatcher;}
//    配置 shiroRealm@Beanpublic ShiroRealm shiroRealm() {ShiroRealm myShiroRealm = new ShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myShiroRealm;}@Beanpublic org.apache.shiro.mgt.SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 注入自定义的realm;securityManager.setRealm(shiroRealm());// 注入缓存管理器;securityManager.setCacheManager(ehCacheManager());//自定义session管理     添加自定义session管理,解决 异常 sessionId(无效)冲突问题。 挤人功能所需配置securityManager.setSessionManager(sessionManager());return securityManager;}/** 开启shiro aop注解支持 使用代理方式;所以需要开启代码支持;*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。*/@Bean@ConditionalOnMissingBeanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();defaultAAP.setProxyTargetClass(true);return defaultAAP;}/** shiro缓存管理器;* 需要注入对应的其它的实体类中-->安全管理器:securityManager可见securityManager是整个shiro的核心;*/@Beanpublic EhCacheManager ehCacheManager() {EhCacheManager ehcache = new EhCacheManager();CacheManager cacheManager = CacheManager.getCacheManager("shiro");if(cacheManager == null){try {cacheManager = CacheManager.create(ResourceUtils.getInputStreamForPath("classpath:ehcache.xml"));} catch (CacheException | IOException e) {e.printStackTrace();}}ehcache.setCacheManager(cacheManager);return ehcache;}/*** 限制同一账号登录同时登录人数控制** @return*/public KickoutSessionControlFilter kickoutSessionControlFilter() {KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();//用于根据会话ID,获取会话进行踢出操作的;kickoutSessionControlFilter.setSessionManager(sessionManager());//使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;kickoutSessionControlFilter.setCacheManager(ehCacheManager());//是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;kickoutSessionControlFilter.setKickoutAfter(false);//同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;kickoutSessionControlFilter.setMaxSession(1);//被踢出后重定向到的地址;kickoutSessionControlFilter.setKickoutUrl("/land?kickout=1");return kickoutSessionControlFilter;}/*** EnterpriseCacheSessionDAO shiro sessionDao层的实现;* 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。*/@Beanpublic EnterpriseCacheSessionDAO enterCacheSessionDAO() {EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();//添加缓存管理器//添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");return enterCacheSessionDAO;}@Beanpublic SimpleCookie sessionIdCookie() {//DefaultSecurityManagerSimpleCookie simpleCookie = new SimpleCookie();//如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。simpleCookie.setHttpOnly(true);
//        用于解决sessionID冲突问题。换个名字simpleCookie.setName("SHRIOSESSIONID");//单位秒simpleCookie.setMaxAge(86400);return simpleCookie;}/*** @描述:sessionManager添加session缓存操作DAO* @return*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(enterCacheSessionDAO());sessionManager.setSessionIdCookie(sessionIdCookie());return sessionManager;}}

然后配置 ShiroRealm(百度翻译: Realm 领域)

import com.wulianwang.manage.model.dbentity.system.UserInfo;
import com.wulianwang.manage.service.system.impl.UserService;
import com.wulianwang.manage.utils.util.Utils;
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.springframework.beans.factory.annotation.Autowired;import java.util.HashSet;
import java.util.Set;import static com.wulianwang.manage.utils.base.UserUtil.setUser;/*** @author liguanhua* @Date: 2019/12/17 11:23* @Description:*/
public class ShiroRealm extends AuthorizingRealm {/*** 方面用于加密 参数:AuthenticationToken是从表单穿过来封装好的对象*/@Autowiredprivate UserService userService;@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("doGetAuthenticationInfo:" + token);// 将AuthenticationToken强转为AuthenticationToken对象UsernamePasswordToken upToken = (UsernamePasswordToken) token;// 获得从表单传过来的用户名String userName = upToken.getUsername();UserInfo user = userService.selectUserByUserName(userName);// 如果用户不存在,抛此异常if (!Utils.isNotEmpty(user)) {throw new UnknownAccountException("无此用户名!");}// 认证的实体信息,可以是username,也可以是用户的实体类对象,这里用的用户名Object principal = userName;// 从数据库中查询的密码Object credentials = user.getPassword();// 当前realm对象的名称,调用分类的getName()String realmName = this.getName();// 创建SimpleAuthenticationInfo对象,并且把username和password等信息封装到里面// 用户密码的比对是Shiro帮我们完成的SimpleAuthenticationInfo info = null;info = new SimpleAuthenticationInfo(principal, credentials, realmName);setUser(user);return info;}// 用于授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("MyShiroRealm的doGetAuthorizationInfo授权方法执行");// User user=(User)// principals.fromRealm(this.getClass().getName()).iterator().next();//获取session中的用户// System.out.println("在MyShiroRealm中AuthorizationInfo(授权)方法中从session中获取的user对象:"+user);// 从PrincipalCollection中获得用户信息Object principal = principals.getPrimaryPrincipal();System.out.println("ShiroRealm  AuthorizationInfo:" + principal.toString());// 根据用户名来查询数据库赋予用户角色,权限(查数据库)Set<String> roles = new HashSet<>();Set<String> permissions = new HashSet<>();
//      2018.09.14更新//      给用户添加user权限 (没有进行判断、对所有的用户给user权限)
//        if("user".equals(principal)){
//            roles.add("user");
//            permissions.add("user:query");
//        }当用户名为admin时 为用户添加权限admin  两个admin可以理解为连个字段
//        if ("admin".equals(principal)) {
//            roles.add("admin");
//            permissions.add("admin:query");
//        }为用户添加visit游客权限,在url中没有为visit权限,所以,所有的操作都没权限
//        if("visit".equals(principal)){
//            roles.add("visit");
//            permissions.add("visit:query");
//        }
//              更新以上代码SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);//添加权限info.setStringPermissions(permissions);return info;// return null;}
}

然后sessiondao SessionDAO

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;import java.io.Serializable;
import java.util.Collection;/*** @author liguanhua* @Date: 2019/12/19 10:36* @Description:*/
public interface SessionDAO {/*如DefaultSessionManager在创建完session后会调用该方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;主要此处返回的ID.equals(session.getId());*/Serializable create(Session session);//根据会话ID获取会话Session readSession(Serializable sessionId) throws UnknownSessionException;//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用void update(Session session) throws UnknownSessionException;//删除会话;当会话过期/会话停止(如用户退出时)会调用void delete(Session session);//获取当前所有活跃用户,如果用户量多此方法影响性能Collection<Session> getActiveSessions();}

然后 ehcache.xml配置 boot 2.0以上应该是需要加 defaultCache 配置。如果你报错提示 default 错误可以加上这个

<ehcache><diskStore path="java.io.tmpdir"/><!-- name 缓存名称 --><!-- maxElementsInMemory 内存中最大缓存对象数,看着自己的heap大小来搞 --><!-- eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false --><!-- maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大 --><!-- overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。--><!-- diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。--><!-- diskPersistent:是否缓存虚拟机重启期数据  --><!-- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒 --><!--timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。--><!--timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。--><!-- clearOnFlush:内存数量最大时是否清除 --><!-- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。--><!-- shiro-activeSessionCache活跃用户session缓存策略 --><defaultCachemaxElementsInMemory="10000"timeToIdleSeconds="120"timeToLiveSeconds="120"maxElementsOnDisk="10000000"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"><!--<persistence strategy="localTempSwap"/>--></defaultCache><cache name="shiro-activeSessionCache"maxElementsInMemory="10000"timeToIdleSeconds="86400"timeToLiveSeconds="86400"maxElementsOnDisk="10000000"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"><!-- <persistence strategy="localTempSwap"/>--></cache><!-- 登录记录缓存 锁定2分钟 --><cache name="passwordRetryCache"maxEntriesLocalHeap="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="0"overflowToDisk="false"statistics="false"></cache>
</ehcache>

然后 配置踢人(挤人)逻辑 KickoutSessionControlFilter


import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author liguanhua* @Date: 2019/12/18 14:32* @Description: 挤人逻辑*/
public class KickoutSessionControlFilter extends AccessControlFilter {private static final Logger logger = LoggerFactory.getLogger(KickoutSessionControlFilter.class);private static SessionDAO sessionDAO;private String kickoutUrl; // 踢出后到的地址private boolean kickoutAfter = false; // 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户private int maxSession = 1; // 同一个帐号最大会话数 默认1private SessionManager sessionManager;private Cache<String, Deque<Serializable>> cache;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}public void setKickoutAfter(boolean kickoutAfter) {this.kickoutAfter = kickoutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}// 设置Cache的key的前缀public void setCacheManager(CacheManager cacheManager) {//必须和ehcache缓存配置中的缓存name一致this.cache = cacheManager.getCache("shiro-activeSessionCache");}@Overrideprotected boolean isAccessAllowed(ServletRequest request,ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {Subject subject = getSubject(request, response);// 没有登录授权 且没有记住我if (!subject.isAuthenticated() && !subject.isRemembered()) {// 如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();logger.debug("==session时间设置:" + String.valueOf(session.getTimeout())+ "===========");try {// 当前用户String username = subject.getPrincipal() + "";logger.debug("===当前用户username:==" + username);Serializable sessionId = session.getId();logger.debug("===当前用户sessionId:==" + sessionId);// 读取缓存用户 没有就存入Deque<Serializable> deque = cache.get(username);logger.debug("===当前deque:==" + deque);if (deque == null) {// 初始化队列deque = new ArrayDeque<Serializable>();}// 如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId)&& session.getAttribute("kickout") == null) {// 将sessionId存入队列deque.push(sessionId);// 将用户的sessionId队列缓存cache.put(username, deque);}// 如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {logger.debug("===deque队列长度:==" + deque.size());Serializable kickoutSessionId = null;// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;if (kickoutAfter) { // 如果踢出后者kickoutSessionId = deque.removeFirst();} else { // 否则踢出前者kickoutSessionId = deque.removeLast();}// 踢出后再更新下缓存队列cache.put(username, deque);try {// 获取被踢出的sessionId的session对象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (kickoutSession != null) {// 设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}} catch (Exception e) {// ignore exceptione.printStackTrace();}}// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址if ((Boolean) session.getAttribute("kickout") != null&& (Boolean) session.getAttribute("kickout") == true) {// 会话被踢出了try {// 退出登录subject.logout();} catch (Exception e) { // ignore}saveRequest(request);logger.debug("==踢出后用户重定向的路径kickoutUrl:" + kickoutUrl);// 重定向WebUtils.issueRedirect(request, response, kickoutUrl);return false;}return true;} catch (Exception e) { // ignore//重定向到登录界面WebUtils.issueRedirect(request, response, "/land");return false;}}
}

UnknownSessionException异常原因

只所以出现这个问题是因为在shiro的DefaultWebSessionManager类中,默认Cookie名称是JSESSIONID,这样的话与servlet容器名冲突, 如jetty, tomcat等默认JSESSIONID, 当跳出shiro servlet时如error-page容器会为JSESSIONID重新分配值导致登录会话丢失!

我们只需要自己指定一个与项目运行容器不冲突的sessionID就好了

上面配置sessionManager(sessionIdCookie)就是为了解决这个问题。使用了SHRIOSESSIONID

service

UserService

/**
             * 根据用户名获取用户信息
             * @param userName
            * @return
          */
          public UserInfo selectUserByUserName(String userName){
              UserInfo user = userMapper.selectUserByUserName(userName);
              return user;
           }

@RestController

@PostMapping(value = "/login")public Message userLogin(@RequestParam Map<String, String> param, HttpSession session) {// 获得当前SubjectSubject currentUser = SecurityUtils.getSubject();String userName = param.get("userName");String password = param.get("password");// 验证用户是否验证,即是否登录
//        if (!currentUser.isAuthenticated()) {String msg = "";// 把用户名和密码封装为 UsernamePasswordToken 对象UsernamePasswordToken token = new UsernamePasswordToken(userName, password);// remembermMe记住密码 关闭浏览器后再次打开浏览器。系统将Cookie 存到本地。可以不用登陆直接进入token.setRememberMe(true);try {// 执行登录.currentUser.login(token);return renderSuccess(map);} catch (IncorrectCredentialsException e) {return renderError(Global.NAME_OR_PWD_ERROR);} catch (ExcessiveAttemptsException e) {msg = "登录失败次数过多";return renderError(msg);} catch (LockedAccountException e) {msg = "帐号已被锁定";return renderError(msg);} catch (DisabledAccountException e) {return renderError(Global.DISABLED_MESSAGE);} catch (ExpiredCredentialsException e) {msg = "帐号已过期";return renderError(msg);} catch (UnknownAccountException e) {msg = "帐号不存在";return renderError(msg);} catch (UnauthorizedException e) {msg = "您没有得到相应的授权";return renderError(msg);} catch (Exception e) {return renderError(msg);}
//        }
//        return renderSuccess("您已登陆");}

记住我

其他详细权限等介绍可看 https://blog.csdn.net/qq_32786139/article/details/82658197

spring boot + shiro 实现登陆 踢出用户功能 (挤人) 以及UnknownSessionException异常问题 记住我功能相关推荐

  1. shiro 删除用户session_我的shiro之旅: 十二 shiro 踢出用户(同一用户只能一处登录)...

    看了一下官网,没有找到关于如何控制同一用户只能一处登录的介绍,网上也没有找到相关的文章.可能有些人会记录用户的登录信息,然后达到踢出用户的效果.这里介绍一个更简单的方法. 如果我们跟shiro的源码, ...

  2. spring boot shiro redis整合基于角色和权限的安全管理-Java编程

    一.概述 本博客主要讲解spring boot整合Apache的shiro框架,实现基于角色的安全访问控制或者基于权限的访问安全控制,其中还使用到分布式缓存redis进行用户认证信息的缓存,减少数据库 ...

  3. Spring Boot Shiro 权限管理

    Spring Boot Shiro 权限管理 标签: springshiro 2016-01-14 23:44 94587人阅读 评论(60) 收藏 举报 本来是打算接着写关于数据库方面,集成MyBa ...

  4. Spring Boot + Shiro 集成

    2019独角兽企业重金招聘Python工程师标准>>> Spring Boot + Shiro 集成 Shiro 是一个流行的 Java 安全框架. 其实 Spring 有一个 Sp ...

  5. Spring Boot Shiro视频 - 身份认证准备工作

    [视频 & 交流平台] à SpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008& ...

  6. linux强制踢出用户,Linux下管理员强行踢出用户的命令使用方法

    Linux强制踢出用户命令: 一.输入w命令查看已登录用户信息 [root@KW_S01_192.168.1.106_A ~]# w 19:22:31 up  2:11,  3 users,  loa ...

  7. (39.3) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    在学习此小节之前您可能还需要学习: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...

  8. TCP多人聊天程序Java实现(群聊,私聊,在线用户,踢出用户)

    本程序在程序 https://blog.csdn.net/joffy/article/details/18079331 的基础是上添加了私聊,踢出用户两个功能. 由客户端和服务器端构成程序,程序借助J ...

  9. Spring Boot电商项目17:用户模块六:注册接口开发之:使用【GlobalExceptionHandler】来全局统一处理异常;(涉及了@ControllerAdvice等注解)

    说明: (1)为什么写这篇博客?:在[Spring Boot电商项目15:用户模块四:注册接口开发:]中,在Service层中遇到了[用户名重复]的情况,然后Service层把这个情况做成了一个异常, ...

最新文章

  1. R语言包_reshape2
  2. ‘%.2f‘ 与 ‘{:.2f}‘.format(w) 区别
  3. [ 一起学React系列 -- 6 ] 秘术之时间旅行-1
  4. Dell服务器相关操作
  5. nyoj 55 懒省事的小明
  6. 正则表达式匹配多行注解/**/
  7. 实验 7 场景运行监控及性能测试结果分析_实验报告--软件功能测试与性能测试实验
  8. access control java_Java Access Controller
  9. html鼠标自动向下滑动,基于JavaScript实现鼠标向下滑动加载div的代码
  10. python如何开发一个程序思路_用python编写一个合格的ftp程序,思路是怎样的?
  11. AutoCAD启动自动加载程序的研究
  12. ASCII码16进制对照表
  13. 找到某个关键字 同义词词林 python_Python从小白到攻城狮(7):函数
  14. 台式计算机有哪些部分组成,常用台式电脑的基本组成
  15. 大数据 | 抖音,一款神奇的APP
  16. python特征数据类型及常用操作对比总结_如何全面解析数据并创造数据故事
  17. bp神经网络实验报告郑航_bp神经网络实验报告
  18. poj 2942 Knights of the Round Table(双连通分量+tarjan+二分图判定)
  19. SLAM导航机器人零基础实战系列:(四)差分底盘设计——2.stm32主控软件设计
  20. Linux中的if-then语句

热门文章

  1. 中国78比特量子计算机,迄今错误率最低量子比特面世 有望推进量子计算机研发...
  2. Microsoft 智能手机(Smartphone)C#开发入门
  3. 免费无账号直接使用openAI的chatGPT
  4. Activiti学习(一)之工作流的介绍和使用
  5. 网络广播风暴产生的原因
  6. 系统设计和数据库设计答辩问题汇总
  7. 数据分析入门之好莱坞百万级评论数据分析
  8. Java中字符串为什么不以\0结尾
  9. 前端路由和 VueRouter
  10. DocumentFragment