文章目录
- 一、项目背景
- 二、页面效果
- 三、仍然存在的问题总结
- 四、代码
- 前端
- 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
的方式实现登录认证,登录认证具有以下功能:
输入用户不存在,提示“用户不存在”
输入密码错误,提示“密码错误”
同一个时刻只允许同一账户登录,如果开第二个窗口再次登录,弹窗“用户已登录”,当点击确认按钮后登录然后踢出第一个登录的人的网站(即:具有踢出功能)
采用sessionId方式传入Cookie,且sessionId有效期为30分钟;如果sessionId国企或者无效则接口报错401
如果接口报错401或者502,则自动跳转到登录页
有退出功能
接口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);,必须访问安全协议下才允许访问你们的系统。