SpringBoot+Shiro开发登录认证功能

在这里插入图片描述

文章目录

  • 一、项目背景
  • 二、页面效果
  • 三、仍然存在的问题总结
  • 四、代码
    • 前端
      • Login.vue(前端登录页)
      • system.js(调用登录接口代码)
      • index.js(配置拦截器处理逻辑)
    • 后端
      • pom.xml
      • LoginControl.java(登录接口)
      • Account.java(账户实体类)
      • CustomEnterpriseCacheSessionDao.java(配置sessionDAO配置缓存cache)
      • CustomRealm.java(授权和认证逻辑)
      • CustomSessionListener.java(用于配置session监听器sessionListener)
      • KickoutSessionFilter.java(踢出功能)
      • NorthAuthoricationToken(封装登录对象用于shiro登录)
      • RequestContextUtil.java(往session添加、查询、移除属性)
      • SessionFilter.java(sessionId校验过滤器)
      • ShiroConfiguration.java(配置shiro的Bean对象)
  • 五、遇到问题总结
    • 问题一:接口401,自动跳转到登录页
    • 问题二、postman调用接口,为啥我的header请求需要添加x-requested-with XMLHttpRequest才能调通
    • 问题三:首页登录的“记住我”干啥用的
    • 问题四:kickout参数干啥用的
    • 问题五:kickout参数啥时候传false和true
    • 问题六:踢出咋实现的
    • 问题七:页面访问登录页比如http://localhost:8082/#,然后发现登陆成功后还是一闪跳转到登录页,就是反反复复,也提示登陆成功了但就是停留在登录页

一、项目背景

项目采用Vue3+nginx+java+SpringBoot+Shiro+MYSQL的方式实现登录认证,登录认证具有以下功能:

  1. 输入用户不存在,提示“用户不存在”
  2. 输入密码错误,提示“密码错误”
  3. 同一个时刻只允许同一账户登录,如果开第二个窗口再次登录,弹窗“用户已登录”,当点击确认按钮后登录然后踢出第一个登录的人的网站(即:具有踢出功能)
  4. 采用sessionId方式传入Cookie,且sessionId有效期为30分钟;如果sessionId国企或者无效则接口报错401
  5. 如果接口报错401或者502,则自动跳转到登录页
  6. 有退出功能
  7. 接口code=200代表登录接口调用成功,code=14代表用户已登录

二、页面效果

登录页

在这里插入图片描述

提示“用户不存在”

在这里插入图片描述

提示“密码错误”

在这里插入图片描述

提示“用户已登陆”

在这里插入图片描述

采用sessionId方式登录

在这里插入图片描述
在这里插入图片描述

无权限报错401

在这里插入图片描述

退出页面

在这里插入图片描述

三、仍然存在的问题总结

仍然存在的问题一:目前接口都加了sessionId认证,但是没做用户菜单权限校验。举例说明:比如我只给test用户分配新增接口功能,但是当我使用PostMan软件调用修改接口也是可以调通,实际这样就不对,创建用户给用户分配权限的时候应该有接口权限校验,如果无权限也应该报错401,比如test用户调用修改接口应该报错401,但是我没写感觉有点复杂,所以现在仍然可以调通。

四、代码

前端

Login.vue(前端登录页)

import {login} from "@/views/pages/system/system.js";

const accountName = ref("")
const accountPwd = ref("")
const rememberMe = ref(false)

const loginConfirm = async () => {
  login({
    "accountName": accountName.value,
    "accountPwd": accountPwd.value,
    "rememberMe": rememberMe.value,
    "kickout": false
  }).then(response => {
    if (response.code == 200) {
      window.Message.success(response.msg)
      localStorage.setItem('principal', JSON.stringify(response.principal));
      if (rememberMe.value) {
        localStorage.setItem('username', accountName.value);
        localStorage.setItem('password', accountPwd.value);
        localStorage.setItem('rememberMe', true);
      } else {
        localStorage.removeItem('username');
        localStorage.removeItem('password');
        localStorage.setItem('rememberMe', false);
      }
      userInfo.userId = '1'
      router.push({
        name: 'topology_topologyView'
      })
    } else if (response.code == 14) {
      userLoggedInVisible.value = true;
    } else {
      window.Message.error(response.msg)
    }
  })

system.js(调用登录接口代码)

export const logout = (data) => {
  return $http({
    url: `/api/logout`,
    method: Method.GET,
    data
  })
}

index.js(配置拦截器处理逻辑)

import axios, { AxiosInstance, AxiosResponse } from "axios";

// 创建http请求的实例对象
const $http = axios.create({
  timeout: 30000,
  withCredentials: false,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8',
    'Access-Control-Allow-Origin': '*',
    'X-Requested-With': 'XMLHttpRequest'
  }
});

// 添加响应拦截器
$http.interceptors.response.use(
  response=> {
    const { data } = response
    if (response.status === 200) {
      return data
    }
  },
  error => {
      if (error.status === 401) {
        // 路由跳转到登录界面
        window.location.href = `${window.location.origin}/#/`
      } else if (error.status == 502) {
        window.location.href = `${window.location.origin}/#/`
    }
      return Promise.resolve({code: error.status})
  }
);


const Method = {
  GET: 'get',
  POST: 'post',
  DELETE: 'delete',
  PUT: 'put',
}

export { $http,  Method };

后端

pom.xml

<dependency>
   <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

LoginControl.java(登录接口)

package com.xnms.client.service.controller.login.user.control;

import com.xnms.client.service.Utility.Constants;
import com.xnms.client.service.Utility.LoginMsgEnum;
import com.xnms.client.service.Utility.LoginResultEnum;
import com.xnms.client.service.config.shiro.KickoutSessionFilter;
import com.xnms.client.service.config.shiro.NorthAuthoricationToken;
import com.xnms.client.service.config.shiro.RequestContextUtil;
import com.xnms.client.service.controller.common.ResponseModel;
import com.xnms.client.service.utils.AddressUtil;
import com.xnms.data.contract.database.db.Account;
import com.xnms.data.contract.database.db.ClientLogType;
import com.xnms.data.contract.database.db.ClientSubLogType;
import com.xnms.data.contract.database.db.UserRole;
import com.xnms.data.contract.database.db.UserVo;
import com.xnms.data.service.api.client.ClientService;
import com.xnms.data.service.service.impl.users.UserServImpl;
import com.xnms.data.service.util.language.LanguageFind;
import io.swagger.annotations.Api;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Api(tags = "登录-退出")
@RestController
@RequestMapping("/api")
public class LoginControl {

    private static final Logger logger = LoggerFactory.getLogger(LoginControl.class);

    @Autowired
    private KickoutSessionFilter kickoutSessionFilter;

    @Autowired
    private ClientService clientService;

    @Autowired
    private LanguageFind languageFind;

    @Autowired
    private UserServImpl userServImpl;

    public ConcurrentMap<String, Account> loginUser = new ConcurrentHashMap<>();

    public ConcurrentMap<String, Account> getLoginUser() {
        return loginUser;
    }



    /**
     * 登录
     * @param loginData
     * @return
     */
    @RequestMapping(value = "login", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> login(HttpServletRequest request, @RequestBody Map<String,String> loginData, HttpServletResponse response) {
        Map<String, Object> loginResultMap = new HashMap<>();
        Integer result = 0;

        try {
            if (Boolean.valueOf(loginData.get("kickout"))) {
                kickoutSessionFilter.kickout(loginData.get("accountName"));
                this.logout();
            }
            result = checkAccountEffect(request, loginData.get("accountName"), loginData.get("accountPwd"));
        } catch (AccountException e) {
            result = Integer.valueOf(e.getMessage());
        }
        if(result==0) {
            Account account = (Account) SecurityUtils.getSubject().getPrincipal();
            loginUser.put("account", account);
            UserVo userVo = clientService.getUserByUserName(account.getUserName()).getData();
            account.setpUserId(userVo.getpUserId());
            account.setUserId(userVo.getUserId().toString());
            String formatString = languageFind.findKey("Query_LogHistoryLogLoginSucceed");
            if (formatString != null && !formatString.isEmpty()) {
                formatString = MessageFormat.format(formatString, account.getUserName(), account.getIp());
            }
            clientService.clientLog(account.getUserName(), ClientLogType.LogLogin, ClientSubLogType.LogLoginSucceed, formatString);
            if (Constants.LOGIN_SUCCESS.equals(result)) {
                RequestContextUtil.setSessionAttribute(Constants.SESSION_ACCOUNT, account);
            }
            //还原登录关闭
            clientService.retoreClosedByUserName(account.getUserName());
            loginResultMap.put("principal", account);
            List<UserRole> roleList = userServImpl.findRoleByAccount(userVo.getUserId());
            loginResultMap.put("roleList", roleList);
        }
        if (result == 0) {
            loginResultMap.put("code", 200);
        } else {
            loginResultMap.put("code", result);
        }
        loginResultMap.put("msg", LoginMsgEnum.getLoginMsg(result));
        return loginResultMap;
    }


    private int checkAccountEffect(HttpServletRequest request, String username, String password) {
        int result = Constants.LOGIN_SUCCESS;
        Subject subject = SecurityUtils.getSubject();
        NorthAuthoricationToken token = new NorthAuthoricationToken(username, password);
        token.setHost(AddressUtil.getTrueAddress(request));
        try {
            subject.login(token);
        } catch (UnknownAccountException e) {
            result = Constants.LOGIN_USER_MISSION;
        } catch (IncorrectCredentialsException e) {
            result = Constants.LOGIN_PASSWORD_ERROR;
        } catch (AccountException e) {
            result = Integer.valueOf(e.getMessage());
        }  catch (AuthenticationException e) {
            result = Constants.USER_NAME_OR_PASSWORD_IS_INCORRECT;
        }
        return result;
    }

    /**
     * 退出
     */
    @RequestMapping(value = "logout", method = RequestMethod.GET)
    @ResponseBody
    public ResponseModel logout() {
        int result = LoginResultEnum.SUCCESS.getKey();
        Account account = (Account) SecurityUtils.getSubject().getPrincipal();
        if (account != null) {
            try {
                SecurityUtils.getSubject().logout();
                Cache cache = kickoutSessionFilter.getCahche();
                cache.remove(account.getUserName());
            } catch (Exception e) {
                result = LoginResultEnum.FAILURE.getKey();
                return ResponseModel.ofError(result);
            } finally {
                clientService.clientLog(account.getUserName(), ClientLogType.LogLogin, ClientSubLogType.LogClientRunning, languageFind.findKey("LOG_ClientExit"));
            }
        }
        return ResponseModel.ofSuccess(result);
    }
}

Account.java(账户实体类)

package com.xnms.data.contract.database.db;

import java.io.Serializable;

public class Account implements Serializable {
    private String userId;
    private String userName;
    private String password;
    private Integer pUserId;
    private String ip;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getpUserId() {
        return pUserId;
    }

    public void setpUserId(Integer pUserId) {
        this.pUserId = pUserId;
    }

    @Override
    public String toString() {
        return "Account{" +
                "userId='" + userId + '\'' +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", pUserId='" + pUserId + '\'' +
                '}';
    }
}

CustomEnterpriseCacheSessionDao.java(配置sessionDAO配置缓存cache)

package com.xnms.client.service.config.shiro;

import org.apache.shiro.cache.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;

import java.io.Serializable;
import java.util.Deque;
import java.util.concurrent.ConcurrentHashMap;

public class CustomEnterpriseCacheSessionDao extends EnterpriseCacheSessionDAO {


    public CustomEnterpriseCacheSessionDao() {
        setCacheManager(new AbstractCacheManager() {
            @Override
            protected Cache<Serializable, Session> createCache(String name) throws CacheException {
                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
            }
        });
        createActiveAccountSessionsCache();
    }

    protected Serializable doCreate(Session session) {

        return super.doCreate(session);
    }

    private  Cache<String, Deque<Serializable>> activeAccountSessions;


    private String activeAccountSessionsCacheName = "activeAccountSessionsCache";


    private  Cache<String, Deque<Serializable>> getActiveAccountSessionsCacheLazy() {
        if (this.activeAccountSessions == null) {
            this.activeAccountSessions = createActiveAccountSessionsCache();
        }
        return activeAccountSessions;
    }


    protected Cache<String, Deque<Serializable>>  createActiveAccountSessionsCache() {
        Cache<String, Deque<Serializable>>  activeAccountSessions = null;
        CacheManager mgr = getCacheManager();
        if (mgr != null) {
            String name = activeAccountSessionsCacheName;
            activeAccountSessions = mgr.getCache(name);
        }
        return activeAccountSessions;
    }
}

CustomRealm.java(授权和认证逻辑)

package com.xnms.client.service.config.shiro;

import com.xnms.client.service.Utility.LoginMsgEnum;
import com.xnms.client.service.utils.AddressUtil;
import com.xnms.data.contract.SingleResponse;
import com.xnms.data.contract.database.db.Account;
import com.xnms.data.contract.database.db.UserFunModule;
import com.xnms.data.contract.database.db.UserRole;
import com.xnms.data.service.api.client.ClientService;
import com.xnms.data.service.service.impl.users.UserServImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CustomRealm extends AuthorizingRealm {

    private  static Logger logger = LoggerFactory.getLogger(CustomRealm.class);

    @Autowired
    private ClientService clientService;

    private EnterpriseCacheSessionDAO sessionDao;

    @Autowired
    private UserServImpl userServImpl;
    
    @Autowired
    private SessionManager sessionManager;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Account account = (Account) principalCollection.fromRealm(getName()).iterator().next();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if (account != null) {
            // 查询帐户角色集合
            Integer userId = Integer.valueOf(account.getUserId());
            List<UserRole> roleList = userServImpl.findRoleByAccount(userId);
            Set<String> roleSet = new HashSet<>();
            for (UserRole userRole : roleList) {
                roleSet.add(userRole.getName());
            }
            info.addRoles(roleSet);
            // 查询帐户权限集合
            List<UserFunModule> permission = clientService.getUserFunModule(account.getUserId()).getData();
            info.setObjectPermissions((Set)permission);
        }
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        NorthAuthoricationToken token = (NorthAuthoricationToken) authenticationToken;
        Account account = new Account();
        String ipAddress = AddressUtil.getTrueAddress(RequestContextUtil.getRequest());
        account = unmsLoginAuthenticationMode(token, ipAddress);
        account.setIp(ipAddress);
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(account, account.getPassword(), null, getName());
        return authenticationInfo;
    }


    /**
     * 登录认证方校验
     * @param token token
     * @param ipAddress ipAddress
     */
    public Account unmsLoginAuthenticationMode(NorthAuthoricationToken token, String ipAddress) {
        logger.info("unmsLoginAuthenticationMode-token:{},ipAddress:{}", token, ipAddress);
        Account account = new Account();
        String username = token.getUsername();
        account.setUserName(username);
        String password = new String(token.getPassword());
        String Md5HashPassword = new Md5Hash(password).toString().toUpperCase();
        account.setPassword(Md5HashPassword);
        SingleResponse<Integer> userRoleResponse = clientService.getUserRole(username);
        Integer userRole = userRoleResponse.getData();
        logger.info("userRole:{}", userRole);
        SingleResponse<Integer> checkUserPasswordResponse = clientService.checkUserPassword(username, Md5HashPassword);
        int checkResult = checkUserPasswordResponse.getData();
        logger.info("checkResult:{}", checkResult);

        if (StringUtils.isEmpty(username)) {
            throw new AccountException(LoginMsgEnum.USER_NAME_IS_NULL.getKey().toString());
        }
        if (StringUtils.isEmpty(password)) {
            throw new AccountException(LoginMsgEnum.PASSWORD_IS_NULL.getKey().toString());
        }
        if (userRole == -1) {
            throw new AccountException(LoginMsgEnum.UNABLE_TO_CONNECT_TO_DATABASE.getKey().toString());
        }
        if (userRole == -2) {
            throw new UnknownAccountException();
        }
        if (checkResult == 1) {
            throw new IncorrectCredentialsException();
        } else if (checkResult == -1) {
            throw new AuthenticationException();
        }

        // 验证帐户是否重复登录
        if (checkAccountOnline(username)) {
            throw new AccountException(LoginMsgEnum.USERLOGINED.getKey().toString());
        }
        return account;
    }

    private boolean checkAccountOnline(String username) {
        boolean onlineStatus = false;
        // 获取缓存中的session信息
        Cache<String, Deque<Serializable>> sessionCache = sessionDao.getCacheManager().getCache("activeAccountSessionsCache");
        Set<String> keySet = sessionCache.keys();
        if (keySet.contains(username) && sessionCache.get(username).size()>0) {
            try {
                Session session = sessionManager.getSession(new DefaultSessionKey(sessionCache.get(username).getLast()));
                Session subjectSession = SecurityUtils.getSubject().getSession();
                // && subjectSession.getHost().equals(session.getHost())
                if (!subjectSession.getId().equals(session.getId()) && session.getAttribute("kickout") == null) {
                    //该用户已登录,需要提示是否踢掉
                    onlineStatus = true;
                    // && subjectSession.getHost().equals(session.getHost())
                } else if (!subjectSession.getId().equals(session.getId())) {
                    sessionCache.get(username).push(subjectSession.getId());
                }
            } catch (Exception e) {
                sessionCache.remove(username);
            }
        }
        return onlineStatus;
    }

    public EnterpriseCacheSessionDAO getSessionDao() {
        return sessionDao;
    }

    public void setSessionDao(EnterpriseCacheSessionDAO sessionDao) {
        this.sessionDao = sessionDao;
    }

    public SessionManager getSessionManager() {
        return sessionManager;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }
}

CustomSessionListener.java(用于配置session监听器sessionListener)

package com.xnms.client.service.config.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;

import java.io.Serializable;
import java.util.Deque;

public class CustomSessionListener implements SessionListener {

    private EnterpriseCacheSessionDAO sessionDAO;


    @Override
    public void onStart(Session session) {

    }

    @Override
    public void onStop(Session session) {
        Cache<String, Deque<Serializable>> cache = sessionDAO.getCacheManager().getCache("activeAccountSessionsCache");
        if (cache!=null) {
            for (String key : cache.keys()) {
                if (cache.get(key).contains(session.getId())) {
                    cache.remove(key);
                    break;
                }
            }
        }
    }

    @Override
    public void onExpiration(Session session) {
        Cache<String, Deque<Serializable>> cache = sessionDAO.getCacheManager().getCache("activeAccountSessionsCache");
        if (cache!=null) {
            for (String key : cache.keys()) {
                if (cache.get(key).contains(session.getId())) {
                    cache.remove(key);
                    break;
                }
            }
        }
    }

    public void setSessionDAO(EnterpriseCacheSessionDAO sessionDAO) {
        this.sessionDAO = sessionDAO;
    }
}

KickoutSessionFilter.java(踢出功能)

package com.xnms.client.service.config.shiro;

import com.xnms.data.contract.database.db.Account;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;

public class KickoutSessionFilter extends AccessControlFilter {
    private String kickoutUrl;
    private boolean kickoutAfter;
    private int maxSession;
    private String specialUser;
    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object value) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if(!subject.isAuthenticated() && !subject.isRemembered()){
            return true;
        }
        Session session = subject.getSession();
        Account account = (Account)subject.getPrincipal();

        if(specialUser!=null && account.getUserName().equalsIgnoreCase(specialUser)){
            return true;
        }
        Serializable sessionId = session.getId();

        Deque<Serializable> deque = cache.get(account.getUserName());

        if(deque==null){
            deque = new LinkedList<>();
            cache.put(account.getUserName(), deque);
        }

        if(!deque.contains(sessionId) && session.getAttribute("kickout")==null){
            deque.push(sessionId);
        }
        kickSession(deque,maxSession);
        if(session.getAttribute("kickout")!=null){
            subject.logout();
            if(isAjax(WebUtils.toHttp(request))){
                WebUtils.toHttp(response).setHeader("sessionStatus","failure");
            }else {
                WebUtils.issueRedirect(request, response, kickoutUrl);
            }
            return false;
        }
        return true;
    }

    public void kickSession(Deque<Serializable> deque,int size){
        while (deque.size()>size){
            Serializable kickoutSessionId = null;
            try {
                if (kickoutAfter) {
                    kickoutSessionId = deque.removeFirst();
                } else {
                    kickoutSessionId = deque.removeLast();
                }
                Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                if (kickoutSession != null) {
                    kickoutSession.setAttribute("kickout", true);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public void kickout(String accountName) {
        Deque<Serializable> deque = cache.get(accountName);
        if (deque!=null) {
            Session session = sessionManager.getSession(new DefaultSessionKey(deque.getLast()));
            session.setAttribute("kickout", true);
        }
    }

    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 setSpecialUser(String specialUser) {
        this.specialUser = specialUser;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setCacheManager(EnterpriseCacheSessionDAO sessionDAO) {
        this.cache = sessionDAO.getCacheManager().getCache("activeAccountSessionsCache");
    }

    public Cache<String, Deque<Serializable>> getCahche(){
        return cache;
    }

    private boolean isAjax(HttpServletRequest request){
        return (request.getHeader("X-Requested-With")!=null && "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With").toString()));
    }
}

NorthAuthoricationToken(封装登录对象用于shiro登录)

package com.xnms.client.service.config.shiro;

import org.apache.shiro.authc.UsernamePasswordToken;

public class NorthAuthoricationToken extends UsernamePasswordToken {

    public NorthAuthoricationToken(String userName, String password) {
        super(userName, (char[])(password != null ? password.toCharArray() : null), false, (String)null);
    }
}

RequestContextUtil.java(往session添加、查询、移除属性)

package com.xnms.client.service.config.shiro;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class RequestContextUtil {
    /**
     * 获取请求线程的request对象
     * @return HttpServletRequest
     */
    public static HttpServletRequest getRequest(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request;
    }

    /**
     * 获取session中指定的属性
     * @param attributeName 待获取的属性名称
     * @return Object
     */
    public static Object getSessionAttribute(String attributeName){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getSession().getAttribute(attributeName);
    }

    /**
     * 添加session属性
     * @param key 设置的属性关键字
     * @param attribute 设置的属性值
     */
    public static void setSessionAttribute(String key, Object attribute){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        request.getSession().setAttribute(key, attribute);
    }

    /**
     * 移除session中的属性
     * @param key 待移除的属性关键字
     */
    public static void removeSessionAttribute(String key){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        request.getSession().removeAttribute(key);
    }
}

SessionFilter.java(sessionId校验过滤器)

package com.xnms.client.service.config.shiro;

import com.xnms.data.contract.database.db.Account;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SessionFilter extends FormAuthenticationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = this.getSubject(request, response);
        if(subject.isAuthenticated()){
            Account account = null;
            try {
                account = (Account)subject.getPrincipal();
            } catch (Exception e) {
                return false;
            }

            if(account!=null){
                return super.isAccessAllowed(request,response,mappedValue);
            }else {
                return false;
            }

        }else{
            return super.isAccessAllowed(request,response,mappedValue);
        }

    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {

                return executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            //如果是Ajax请求,不跳转登录
            if (isAjax(WebUtils.toHttp(httpServletRequest))){
                httpServletResponse.setStatus(401);
            } else {
                saveRequestAndRedirectToLogin(request, response);
            }
            return false;
        }
    }
    private boolean isAjax(HttpServletRequest request){
        return (request.getHeader("X-Requested-With")!=null && "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With").toString()));
    }
}

ShiroConfiguration.java(配置shiro的Bean对象)

package com.xnms.client.service.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class ShiroConfiguration {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilter() {
        ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();
        factory.setSecurityManager(getDefaultWebSecurityManager());
        factory.setLoginUrl("/api/login");
        Map<String, Filter> filtersMap = new HashMap<>();
        filtersMap.put("authc", getSessionFilter());
        filtersMap.put("kickout", getKickoutSessionFilter());
        factory.setFilters(filtersMap);
        Map<String, String> definitionMap = new LinkedHashMap<>();
        definitionMap.put("/api/i18n/**", "anon");
        definitionMap.put("/api/config", "anon");
        definitionMap.put("/api/login", "anon");
        definitionMap.put("/api/**", "authc,kickout");
        definitionMap.put("/swagger-ui.html", "anon");
        definitionMap.put("/swagger-resources/**", "anon");
        definitionMap.put("/swagger/**", "anon");
        definitionMap.put("/**/v2/api-docs", "anon");
        factory.setFilterChainDefinitionMap(definitionMap);
        return factory;
    }

    public SessionFilter getSessionFilter() {
        return new SessionFilter();
    }

    @Bean(name = "kickoutSessionFilter")
    public KickoutSessionFilter getKickoutSessionFilter() {
        KickoutSessionFilter kickoutFilter = new KickoutSessionFilter();
        kickoutFilter.setKickoutUrl("/api/login");
        kickoutFilter.setKickoutAfter(false);
        kickoutFilter.setMaxSession(1);
        kickoutFilter.setSessionManager(getDefaultWebSessionManager());
        kickoutFilter.setCacheManager(getEnterpriseCacheSessionDAO());
        return kickoutFilter;
    }


    @Bean(name = "customRealm")
    public CustomRealm getCustomRealm() {
        CustomRealm customRealm = new CustomRealm();
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1);
        customRealm.setCredentialsMatcher(matcher);
        customRealm.setCachingEnabled(true);
        customRealm.setSessionManager(getDefaultWebSessionManager());
        customRealm.setSessionDao(getEnterpriseCacheSessionDAO());
        return customRealm;
    }

    @Bean(name = "sessionManager")
    public DefaultWebSessionManager getDefaultWebSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(1800000);
        sessionManager.setSessionValidationInterval(60000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionValidationScheduler(sessionValidationScheduler(sessionManager));
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(getSessionIdCookie());
        sessionManager.setSessionIdUrlRewritingEnabled(true);
        sessionManager.setSessionListeners(sessionListener());
        sessionManager.setSessionDAO(getEnterpriseCacheSessionDAO());
        return sessionManager;
    }

    @Bean
    public ExecutorServiceSessionValidationScheduler sessionValidationScheduler(DefaultWebSessionManager sessionManager) {
        ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
        scheduler.setInterval(18000);
        scheduler.setSessionManager(sessionManager);
        return scheduler;
    }

    @Bean(name = "sessionIdGenerator")
    public JavaUuidSessionIdGenerator getSessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }

    @Bean(name = "sessionDAO")
    public CustomEnterpriseCacheSessionDao getEnterpriseCacheSessionDAO() {
        CustomEnterpriseCacheSessionDao sessionDao = new CustomEnterpriseCacheSessionDao();
        sessionDao.setSessionIdGenerator(getSessionIdGenerator());
        sessionDao.setActiveSessionsCacheName("activeAccountSessionsCache");
        return sessionDao;
    }

    @Bean(name = "sessionIdCookie")
    public SimpleCookie getSessionIdCookie() {
        SimpleCookie sessionCookie = new SimpleCookie("mldn-session-id");
        sessionCookie.setHttpOnly(true);
        return sessionCookie;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(getCustomRealm());
        manager.setSessionManager(getDefaultWebSessionManager());
        return manager;
    }

    @Bean(name = "sessionListener")
    public List<SessionListener> sessionListener() {
        List<SessionListener> list = new ArrayList<>();
        CustomSessionListener listener = new CustomSessionListener();
        listener.setSessionDAO(getEnterpriseCacheSessionDAO());
        list.add(listener);
        return list;
    }
}

五、遇到问题总结

问题一:接口401,自动跳转到登录页

原因:前端未配置拦截器

解决方案:index.js中添加响应拦截器

// 添加响应拦截器
$http.interceptors.response.use(
  response=> {
    const { data } = response
    if (response.status === 200) {
      return data
    }
  },
  error => {
      if (error.status === 401) {
        // 路由跳转到登录界面
        window.location.href = `${window.location.origin}/#/`
      } else if (error.status == 502) {
        window.location.href = `${window.location.origin}/#/`
    }
      return Promise.resolve({code: error.status})
  }
);

问题二、postman调用接口,为啥我的header请求需要添加x-requested-with XMLHttpRequest才能调通

原因:发生在调接口传参sessionId进行解析,也就是进入SessionFilter过滤器,如果不传参x-requested-with XMLHttpRequest就会报错“请求方式不对”?

未传x-requested-with XMLHttpRequest

在这里插入图片描述

传参x-requested-with XMLHttpRequest

在这里插入图片描述
分析:因为shiro默认自带的接口sessionId认证是GET请求方式,哪怕你postman采用POST请求也会报请求方式不对这个错,所以我自定义过滤器,封装方法private boolean isAjax(HttpServletRequest request)校验,只要header带x-requested-with XMLHttpRequest,就不会报请求方式错误。

//如果是Ajax请求,不跳转登录
if (isAjax(WebUtils.toHttp(httpServletRequest))){
     httpServletResponse.setStatus(401);
 } else {
     saveRequestAndRedirectToLogin(request, response);
 }

问题三:首页登录的“记住我”干啥用的

答案:如果选择“记住我”,当退出后自动跳转到登录页,密码那栏仍然有填充值;反之密码栏就会清空内容。

问题四:kickout参数干啥用的

答案:用于相同账户登录,进行踢出。

问题五:kickout参数啥时候传false和true

答案:当首次登陆默认传参kickout=false;只有当登录返回code=14时代表当前用户已登录,这时候在登陆传参就是kickout=true

问题六:踢出咋实现的

答案:当调用login接口时如果传参kickout=true直接踢出


if (Boolean.valueOf(loginData.get("kickout"))) {
	kickoutSessionFilter.kickout(loginData.get("accountName"));
    this.logout();
}

问题七:页面访问登录页比如http://localhost:8082/#,然后发现登陆成功后还是一闪跳转到登录页,就是反反复复,也提示登陆成功了但就是停留在登录页

在这里插入图片描述

@Bean(name = "sessionIdCookie")
    public SimpleCookie getSessionIdCookie() {
        SimpleCookie sessionCookie = new SimpleCookie("mldn-session-id");
        sessionCookie.setHttpOnly(true);
        sessionCookie.setSecure(true);
        return sessionCookie;
    }

错误原因:因为你设置sessionCookie.setSecure(true),这就说明你访问地址必须是https安全协议下,服务器才会把sessionId设置进Cookie中,而因为你访问http://localhost:8082/#,当登陆成功后会加载别的接口这时候未传参sessionId就会报错401,这时候就会自动跳转到登录页。

解决方案:删除这行sessionCookie.setSecure(true);,这样http或者https的url就都可以访问了,当然这是本地测试场景,实际生产环境必须开启sessionCookie.setSecure(true);,必须访问安全协议下才允许访问你们的系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/978960.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VUE2.0+elementUI+腾讯云点播上传视频

“vue”: “^2.6.11”, TcVod&#xff1a; “vod-js-sdk-v6”: “^1.7.0”, “element-ui”: “^2.15.6”, 直接上代码 上传视频加上传封面 在这里插入代码片 <template><div><p>上传封面</p><el-uploadclass"avatar-uploader"actio…

LlamaFactory-webui:训练大语言模型的入门级教程

LlamaFactory是一个开源框架&#xff0c;支持多种流行的语言模型&#xff0c;及多种微调技术&#xff0c;同时&#xff0c;以友好的交互式界面&#xff0c;简化了大语言模型的学习。 本章内容&#xff0c;从如何拉取&#xff0c;我已经搭建好的Llamafactory镜像开始&#xff0…

音频进阶学习十六——LTI系统的差分方程与频域分析一(频率响应)

文章目录 前言一、差分方程的有理式1.差分方程的有理分式2.因果系统和ROC3.稳定性与ROC 二、频率响应1.定义2.幅频响应3.相频响应4.群延迟 总结 前言 本篇文章会先复习Z变换的有理分式&#xff0c;这是之前文章中提过的内容&#xff0c;这里会将差分方程和有理分式进行结合来看…

一周学会Flask3 Python Web开发-Jinja2模板访问对象

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 如果渲染模板传的是对象&#xff0c;如果如何来访问呢&#xff1f; 我们看下下面示例&#xff1a; 定义一个Student类 cla…

APISIX Dashboard上的配置操作

文章目录 登录配置路由配置消费者创建后端服务项目配置上游再创建一个路由测试 登录 http://192.168.10.101:9000/user/login?redirect%2Fdashboard 根据docker 容器里的指定端口&#xff1a; 配置路由 通过apisix 的API管理接口来创建&#xff08;此路由&#xff0c;直接…

【学习笔记】三维点云空洞修复介绍(二)Grids-based

1 研究领域内网格的定义 网格&#xff08;有研究也成格网&#xff09;指的是三维多边形网格模型&#xff0c;简称“网格”。简单的定义为&#xff1a;由多边形集合定义&#xff0c;用以表示三维模型表面轮廓的拓扑和空间结构&#xff0c;英文为polygon mesh或mesh。 其中&am…

助力DeepSeek私有化部署服务:让企业AI落地更简单、更安全

在数字化转型的浪潮中&#xff0c;越来越多的企业选择私有化部署AI技术&#xff0c;以保障数据安全、提升业务效率并实现自主可控。DeepSeek作为行业领先的AI开源技术&#xff0c;其技术可以支持企业私有化部署&#xff0c;企业需要一站式服务私有化部署&#xff0c;涵盖硬件采…

嵌入式开发:傅里叶变换(4):在 STM32上面实现FFT(基于STM32L071KZT6 HAL库+DSP库)

目录 步骤 1&#xff1a;准备工作 步骤 2&#xff1a;创建 Keil 项目&#xff0c;并配置工程 步骤 3&#xff1a;在MDK工程上添加 CMSIS-DSP 库 步骤 5&#xff1a;编写代码 步骤 6&#xff1a;配置时钟和优化 步骤 7&#xff1a;调试与验证 步骤 8&#xff1a;优化和调…

SQLark 数据迁移|断点续迁已上线(Oracle-达梦)

数据迁移是 SQLark 最受企业和个人用户欢迎的功能之一&#xff0c;截止目前已帮助政府、金融、能源、通信等 50 家单位完成从 Oracle、MySQL 到达梦的全量迁移&#xff0c;自动化迁移成功率达 96% 以上。 在 Oracle 到达梦数据库迁移过程中&#xff0c;SQLark V3.3 新增 断点续…

从哪里下载WinPrefetchView最安全?

WinPrefetchView 是一款用于读取和显示 Windows 系统中预读取文件&#xff08;Prefetch&#xff09;信息的工具&#xff0c;能够帮助用户了解系统启动时加载了哪些文件以及应用程序的运行情况。为了确保下载安全&#xff0c;建议从以下可信来源获取&#xff1a; 1. MajorGeeks…

计算机毕业设计SpringBoot+Vue.js智能物流管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

深度学习每周学习总结Y1(Yolov5 调用官方权重进行检测 )

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客Y1中的内容 &#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 ** 注意该训练营出现故意不退押金&#xff0c;恶意揣测偷懒用假的结果冒充真实打卡记录&#xff0c;在提出能够拿到视频录像…

内容中台是什么?内容管理平台解析

内容中台的核心价值 现代企业数字化转型进程中&#xff0c;内容中台作为中枢系统&#xff0c;通过构建统一化的内容管理平台实现数据资产的高效整合与智能调度。其核心价值体现在打破传统信息孤岛&#xff0c;将分散于CRM、ERP等系统的文档、知识库、产品资料进行标准化归集&a…

多模态人物视频驱动技术回顾与业务应用

一种新的商品表现形态&#xff0c;内容几乎存在于手淘用户动线全流程&#xff0c;例如信息流种草内容、搜索消费决策内容、详情页种草内容等。通过低成本、高时效的AIGC内容生成能力&#xff0c;能够从供给端缓解内容生产成本高的问题&#xff0c;通过源源不断的低成本供给倒推…

additional-spring-configuration-metadata.json实现springboot自定义提示

在配置additional-spring-configuration-metadata.json文件后&#xff0c;在开发人员的IDE工具使用个人编写的配置读取很有效的在application.properties或application.yml文件下完成提示。 配置元数据文件位于jar下面。 META-INF/spring-configuration-metadata.json它们使用简…

使用vscode导出Markdown的PDF无法显示数学公式的问题

我的硬件环境是M2的MacBook air&#xff0c;在vscode中使用了Markdown PDF来导出md文件对应的PDF。但不管导出html还是PDF文件&#xff0c;数学公式都是显示的源代码。 我看了许多教程&#xff0c;给的是这个方法&#xff1a;在md文件对应的html文件中加上以下代码&#xff1a…

SpringBoot3—快速入门

一、简介 &#xff08;1&#xff09;前置知识 Java17Spring、SpringMVC、MyBatisMaven、IDEA &#xff08;2&#xff09;环境要求 &#xff08;3&#xff09;SpringBoot3是什么 核心概念&#xff1a;Spring Boot 底层是 Spring&#xff0c;能简单、快速地创建一个独立的、生…

DeepSeek 15天指导手册——从入门到精通 PDF(附下载)

DeepSeek使用教程系列--DeepSeek 15天指导手册——从入门到精通pdf下载&#xff1a; https://pan.baidu.com/s/1PrIo0Xo0h5s6Plcc_smS8w?pwd1234 提取码: 1234 或 https://pan.quark.cn/s/2e8de75027d3 《DeepSeek 15天指导手册——从入门到精通》以系统化学习路径为核心&…

AWS S3 如何设置公开访问权限?

1.让整个bucket都有公开访问权限 1.1关闭【阻止公共读】 1.2关闭ACL访问控制 1.3打开桶策略 这样桶内所有的图片就能访问了 2.只开放特定文件让其具有访问权限&#xff1f; 2.1关闭【阻止公共读】 如之前的图示 2.2打开ACL控制 2.3单个文件打开公共读

深入了解 Python 中的 MRO(方法解析顺序)

文章目录 深入了解 Python 中的 MRO&#xff08;方法解析顺序&#xff09;什么是 MRO&#xff1f;如何计算 MRO&#xff1f;C3 算法的合并规则C3 算法的合并步骤示例&#xff1a;合并过程解析 MRO 解析失败的场景使用 mro() 方法查看 MRO示例 1&#xff1a;基本用法 菱形继承与…