文章目录
- 一、项目背景
- 二、页面效果
- 三、仍然存在的问题总结
- 四、代码
- 前端
- 后端
- 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对象)
- 五、遇到问题总结
一、项目背景
项目采用Vue3+nginx+java+SpringBoot+Shiro+MYSQL
的方式实现登录认证,登录认证具有以下功能:
输入用户不存在,提示“用户不存在”
输入密码错误,提示“密码错误”
同一个时刻只允许同一账户登录,如果开第二个窗口再次登录,弹窗“用户已登录”,当点击确认按钮后登录然后踢出第一个登录的人的网站(即:具有踢出功能)
采用sessionId方式传入Cookie,且sessionId有效期为30分钟;如果sessionId国企或者无效则接口报错401
如果接口报错401或者502,则自动跳转到登录页
有退出功能
接口code=200代表登录接口调用成功,code=14代表用户已登录
二、页面效果
登录页
提示“用户不存在”
提示“密码错误”
提示“用户已登陆”
采用sessionId方式登录
无权限报错401
退出页面
三、仍然存在的问题总结
仍然存在的问题一
:目前接口都加了sessionId认证,但是没做用户菜单权限校验。举例说明:比如我只给test用户分配新增接口功能,但是当我使用PostMan软件调用修改接口也是可以调通,实际这样就不对,创建用户给用户分配权限的时候应该有接口权限校验,如果无权限也应该报错401,比如test用户调用修改接口应该报错401,但是我没写感觉有点复杂,所以现在仍然可以调通。
四、代码
前端
Login.vue(前端登录页)
java">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(调用登录接口代码)
java">export const logout = (data) => {return $http({url: `/api/logout`,method: Method.GET,data})
}
index.js(配置拦截器处理逻辑)
java">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 } = responseif (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
java"><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version>
</dependency>
java_157">LoginControl.java(登录接口)
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);@Autowiredprivate KickoutSessionFilter kickoutSessionFilter;@Autowiredprivate ClientService clientService;@Autowiredprivate LanguageFind languageFind;@Autowiredprivate 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)@ResponseBodypublic 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)@ResponseBodypublic 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);}
}
java_324">Account.java(账户实体类)
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;}@Overridepublic String toString() {return "Account{" +"userId='" + userId + '\'' +", userName='" + userName + '\'' +", password='" + password + '\'' +", pUserId='" + pUserId + '\'' +'}';}
}
javasessionDAOcache_389">CustomEnterpriseCacheSessionDao.java(配置sessionDAO配置缓存cache)
java">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() {@Overrideprotected 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;}
}
java_445">CustomRealm.java(授权和认证逻辑)
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);@Autowiredprivate ClientService clientService;private EnterpriseCacheSessionDAO sessionDao;@Autowiredprivate UserServImpl userServImpl;@Autowiredprivate SessionManager sessionManager;@Overrideprotected 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;}@Overrideprotected 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;}
}
javasessionsessionListener_620">CustomSessionListener.java(用于配置session监听器sessionListener)
java">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;@Overridepublic void onStart(Session session) {}@Overridepublic 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;}}}}@Overridepublic 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;}
}
java_674">KickoutSessionFilter.java(踢出功能)
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;@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object value) 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();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()));}
}
shiro_806">NorthAuthoricationToken(封装登录对象用于shiro登录)
java">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);}
}
javasession_820">RequestContextUtil.java(往session添加、查询、移除属性)
java">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);}
}
javasessionId_870">SessionFilter.java(sessionId校验过滤器)
java">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 {@Overrideprotected 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);}}@Overrideprotected 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()));}
}
javashiroBean_935">ShiroConfiguration.java(配置shiro的Bean对象)
java">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;}@Beanpublic 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中添加响应拦截器
java">// 添加响应拦截器
$http.interceptors.response.use(response=> {const { data } = responseif (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,就不会报请求方式错误。
java">//如果是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直接踢出
java">
if (Boolean.valueOf(loginData.get("kickout"))) {kickoutSessionFilter.kickout(loginData.get("accountName"));this.logout();
}
问题七:页面访问登录页比如http://localhost:8082/#,然后发现登陆成功后还是一闪跳转到登录页,就是反反复复,也提示登陆成功了但就是停留在登录页
java">@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);,必须访问安全协议下才允许访问你们的系统。