一、需求描述
应用环境中,主服务器的荡掉之后,在不重新登录的情况下,自动访问备服务器的应用。本文介绍用户登录之后,通过Shiro管理Session,并且把Session缓存到redis(已做主备)中,停掉其中主服务器应用,自动切换到被服务器上。
二、实现思路
1、用nginx做代理,搭建主备服务器应用,实现双机热备,存在的问题:session无法共享
2、服务端将session保存到redis中,避免session失效
三、软件列表
1、redis,下载地址:https://redis.io/
2、nginx,下载地址:http://nginx.org/en/download.html
四、搭建环境
1、nginx.conf 添加以下配置
upstream tomcat {
server 192.168.1.202:8080 ;
server 192.168.1.203:8080 ;
}
location /meta {
proxy_pass http://tomcat;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
2、spring-shiro.xml 添加以下配置
<!-- SecurityManager实例 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
<!--<property name="realm" ref="userRealm"/>-->
<property name="authenticator" ref="realmAuthenticator"/>
<!--将session托管给redis进行管理,便于搭建集群系统-->
<property name="sessionManager" ref="webSessionManager"/>
<property name="realms">
<list>
<ref bean="userRealm"/>
</list>
</property>
</bean>
<!--redis管理session-->
<bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="redisSessionDao"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="globalSessionTimeout" value="1800000"/><!-- 30分钟 -->
<property name="sessionIdCookie" ref="sharesession"/>
<property name="sessionIdUrlRewritingEnabled" value="false"/>
<!-- 定时检查失效的session -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
</bean>
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,对应的默认是 JSESSIONID -->
<constructor-arg name="name" value="SHAREJSESSIONID"/>
<!-- jsessionId的path为 / 用于多个系统共享jsessionId -->
<property name="path" value="/"/>
</bean>
<bean id="redisSessionDao" class="umbrella.tyrant.cache.redisSession.RedisSessionDao">
<property name="expire" value="1800000"/>
</bean>
3、由Redis来管理session,包括session创建、读取、删除等,还可以统计在线用户,下面是核心代码RedisSessionDao的实现:
import com.cnpc.framework.base.dao.RedisDao;
import com.cnpc.framework.base.entity.BaseEntity;
import com.cnpc.framework.constant.RedisConstant;
import com.cnpc.framework.utils.StrUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;
import javax.annotation.Resource;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* 通过如下方式调用
*/
public class RedisSessionDao extends AbstractSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);
private long expire;
@Resource
private RedisDao redisDao;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.error("session id is null");
return null;
}
logger.debug("Read Redis.SessionId=" + new String(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));
Session session = (Session) SerializationUtils.deserialize(redisDao.getByte(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));
return session;
}
@Override
public void update(Session session) throws UnknownSessionException {
this.saveSession(session);
}
int i=0;
public void saveSession(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
session.setTimeout(expire);
long timeout = expire / 1000;
//保存用户会话
redisDao.add(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()), timeout, SerializationUtils.serialize(session));
//获取用户id
String uid = getUserId(session);
if (!StrUtil.isEmpty(uid)) {
//保存用户会话对应的UID
try {
redisDao.add(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()), timeout, uid.getBytes("UTF-8"));
//保存在线UID
redisDao.add(this.getKey(RedisConstant.UID_PRE, uid), timeout, ("online"+(i++)+"").getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
logger.error("getBytes error:" + ex.getMessage());
}
}
}
public String getUserId(Session session) {
SimplePrincipalCollection pricipal = (SimplePrincipalCollection) session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY");
if (null != pricipal) {
return pricipal.getPrimaryPrincipal().toString();
}
return null;
}
public String getKey(String prefix, String keyStr) {
return prefix + keyStr;
}
@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
//删除用户会话
redisDao.delete(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()));
//获取缓存的用户会话对应的UID
String uid = redisDao.get(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
//删除用户会话sessionid对应的uid
redisDao.delete(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
//删除在线uid
redisDao.delete(this.getKey(RedisConstant.UID_PRE, uid));
//删除用户缓存的角色
redisDao.delete(this.getKey(RedisConstant.ROLE_PRE, uid));
//删除用户缓存的权限
redisDao.delete(this.getKey(RedisConstant.PERMISSION_PRE, uid));
}
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<>();
Set<String> keys = redisDao.keys(RedisConstant.SHIRO_REDIS_SESSION_PRE + "*");
if (keys != null && keys.size() > 0) {
for (String key : keys) {
Session s = (Session) SerializationUtils.deserialize(redisDao.getByte(key));
sessions.add(s);
}
}
return sessions;
}
/**
* 当前用户是否在线
*
* @param uid 用户id
* @return
*/
public boolean isOnLine(String uid) {
Set<String> keys = redisDao.keys(RedisConstant.UID_PRE + uid);
if (keys != null && keys.size() > 0) {
return true;
}
return false;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
}
4、在realm的doGetAuthorizationInfo
中,获取的SimpleAuthorizationInfo authorizationInfo
会存在缓存中,本文也用Redis进行缓存;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
// (可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principals);
SecurityUtils.getSubject().logout();
return null;
}
if (principals == null) {
throw new AuthorizationException("parameters principals is null");
}
//获取已认证的用户名(登录名)
String userId=(String)super.getAvailablePrincipal(principals);
if(StrUtil.isEmpty(userId)){
return null;
}
Set<String> roleCodes=roleService.getRoleCodeSet(userId);
//默认用户拥有所有权限
Set<String> functionCodes=functionService.getAllFunctionCode();
/* Set<String> functionCodes=functionService.getFunctionCodeSet(roleCodes);*/
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roleCodes);
authorizationInfo.setStringPermissions(functionCodes);
return authorizationInfo;
}
5、RedisCacheManager.java实现
import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class RedisCacheManager implements CacheManager {
private static final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);
// fast lookup by name map
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
@Resource
private RedisDao redisDao;
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("获取名称为: " + name + " 的RedisCache实例");
Cache c = caches.get(name);
if (c == null) {
c = new RedisCache<K, V>(redisDao);
caches.put(name, c);
}
return c;
}
}
6、RedisCache.java实现
import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;
import java.util.*;
public class RedisCache<K, V> implements Cache<K, V> {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The wrapped Jedis instance.
*/
//private RedisManager redisDao;
private RedisDao redisDao;
/**
* The Redis key prefix for the sessions
*/
private String keyPrefix = "shiro_redis_session:";
/**
* Returns the Redis session keys
* prefix.
*
* @return The prefix
*/
public String getKeyPrefix() {
return keyPrefix;
}
/**
* Sets the Redis sessions key
* prefix.
*
* @param keyPrefix The prefix
*/
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/**
* 通过一个JedisManager实例构造RedisCache
*/
public RedisCache(RedisDao redisDao) {
if (redisDao == null) {
throw new IllegalArgumentException("Cache argument cannot be null.");
}
this.redisDao = redisDao;
}
/**
* Constructs a redisDao instance with the specified
* Redis manager and using a custom key prefix.
*
* @param redisDao The redisDao manager instance
* @param prefix The Redis key prefix
*/
public RedisCache(RedisDao redisDao,
String prefix) {
this(redisDao);
// set the prefix
this.keyPrefix = prefix;
}
/**
* 获得byte[]型的key
*
* @param key
* @return
*/
private byte[] getByteKey(K key) {
if (key instanceof String) {
String preKey = this.keyPrefix + key;
return preKey.getBytes();
} else {
return SerializationUtils.serialize(key);
}
}
@Override
public V get(K key) throws CacheException {
logger.debug("根据key从Redis中获取对象 key [" + key + "]");
try {
if (key == null) {
return null;
} else {
byte[] rawValue = redisDao.getByte(key.toString());
@SuppressWarnings("unchecked")
V value = (V) SerializationUtils.deserialize(rawValue);
return value;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V put(K key, V value) throws CacheException {
logger.debug("根据key从存储 key [" + key + "]");
try {
redisDao.set(key.toString(), SerializationUtils.serialize(value));
return value;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V remove(K key) throws CacheException {
logger.debug("从redis中删除 key [" + key + "]");
try {
V previous = get(key);
redisDao.delete(key.toString());
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public void clear() throws CacheException {
logger.debug("从redis中删除所有元素");
try {
redisDao.flushDB();
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public int size() {
try {
Long longSize = new Long(redisDao.dbSize());
return longSize.intValue();
} catch (Throwable t) {
throw new CacheException(t);
}
}
@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
try {
Set<String> keys = redisDao.keys(this.keyPrefix + "*");
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
} else {
Set<K> newKeys = new HashSet<K>();
for (String key : keys) {
newKeys.add((K) key);
}
return newKeys;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public Collection<V> values() {
try {
Set<String> keys = redisDao.keys(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
List<V> values = new ArrayList<V>(keys.size());
for (String key : keys) {
@SuppressWarnings("unchecked")
V value = get((K) key);
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
} else {
return Collections.emptyList();
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
}
7、RedisDao.java
import java.util.List;
import java.util.Set;
/**
* RedisDao操作数据接口
*
*/
public interface RedisDao {
<T> boolean add(final String key, final T obj);
/**
* setNx
*
* @param key
* @param value
* @return
*/
boolean add(final String key, final String value);
<T> boolean add(final String key, final List<T> list);
void delete(final String key);
void delete(final List<String> keys);
<T> boolean update(final String key, final T obj);
boolean update(final String key, final String value);
/**
* 保存 不存在则新建,存在则更新
*
* @param key
* @param value
* @return
*/
boolean save(final String key, final String value);
<T> boolean save(final String key, final T obj);
<T> T get(final String key, final Class clazz);
<T> List<T> getList(final String key, final Class<T> clazz);
byte[] getByte(final String key);
String get(final String key);
<T> void add(final String key, final long timeout, final T obj);
void add(final String key, final long timeout, final byte[] object);
Set<String> keys(String pattern);
boolean exist(final String key);
boolean set(final String key, final byte[] value);
boolean flushDB();
long dbSize();
}
8、RedisDaoImpl.java实现
import com.alibaba.fastjson.JSON;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import umbrella.tyrant.util.StringUtils;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* RedisDao操作数据接口实现类
*
*/
@Service("redisDao")
public class RedisDaoImpl implements RedisDao{
@Resource
protected RedisTemplate<String, Serializable> redisTemplate;
/**
* 设置redisTemplate
*
* @param redisTemplate the redisTemplate to set
*/
public void setRedisTemplate(RedisTemplate<String, Serializable> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate<String, Serializable> getRedisTemplate() {
return redisTemplate;
}
/**
* 获取 RedisSerializer
*/
private RedisSerializer<String> getRedisSerializer() {
return redisTemplate.getStringSerializer();
}
@Override
public <T> boolean add(final String key, final T obj) {
return add(key, JSON.toJSONString(obj));
}
@Override
public <T> void add(final String key, final long timeout, final T obj) {
redisTemplate.execute(new RedisCallback() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
final byte[] object = serializer.serialize(JSON.toJSONString(obj));
add(key,timeout,object);
return null;
}
});
}
@Override
public void add(final String key, final long timeout,final byte[] object) {
redisTemplate.execute(new RedisCallback() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
connection.setEx(keyStr, timeout, object);
return null;
}
});
}
@Override
public boolean add(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] object = serializer.serialize(value);
return connection.setNX(keyStr, object);
}
});
return result;
}
@Override
public <T> boolean add(final String key, final List<T> list) {
return this.add(key, JSON.toJSONString(list));
}
@Override
public void delete(final String key) {
List<String> list = new ArrayList<>();
list.add(key);
this.delete(list);
}
@Override
public void delete(final List<String> keys) {
redisTemplate.delete(keys);
}
@Override
public <T> boolean update(final String key, final T obj) {
return this.update(key, JSON.toJSONString(obj));
}
@Override
public boolean update(final String key, final String value) {
if (get(key) == null) {
throw new NullPointerException("数据行不存在, key = " + key);
}
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] valueStr = serializer.serialize(value);
connection.set(keyStr, valueStr);
return true;
}
});
return result;
}
@Override
public boolean save(String key, String value) {
if (StringUtils.isEmpty(get(key))) {
return this.add(key, value);
} else {
return this.update(key, value);
}
}
@Override
public <T> boolean save(String key, T obj) {
if (get(key, obj.getClass()) == null) {
return this.add(key, obj);
} else {
return this.update(key, obj);
}
}
@Override
public <T> T get(final String key, final Class clazz) {
T result = redisTemplate.execute(new RedisCallback<T>() {
public T doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return (T) JSON.parseObject(valueStr, clazz);
}
});
return result;
}
@Override
public <T> List<T> getList(final String key, final Class<T> clazz) {
List<T> result = redisTemplate.execute(new RedisCallback<List<T>>() {
public List<T> doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return JSON.parseArray(valueStr, clazz);
}
});
return result;
}
@Override
public String get(final String key) {
String result = redisTemplate.execute(new RedisCallback<String>() {
public String doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return valueStr;
}
});
return result;
}
@Override
public byte[] getByte(final String key) {
byte[] result = redisTemplate.execute(new RedisCallback<byte[]>() {
public byte[] doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
return value;
}
});
return result;
}
@Override
public Set<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
@Override
public boolean exist(final String key){
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
return connection.exists(keyStr);
}
});
return result;
}
@Override
public boolean set(final String key,final byte[] value){
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
connection.set(keyStr, value);
return true;
}
});
return result;
}
public boolean flushDB(){
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
connection.flushDb();
return true;
}
});
return result;
}
public long dbSize(){
long result = redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.dbSize();
}
});
return result;
}
}
9、RedisCacheManager.java 常量
/**
* redisSession常量
*
*/
public class RedisConstant {
/**
* shiro-redis的session对象前缀
*/
public static final String SHIRO_REDIS_SESSION_PRE = "shiro_session:";
/**
* 存放uid的对象前缀
*/
public static final String SHIRO_SESSION_PRE = "shiro_sessionid:";
/**
* 存放uid当前状态状态的前缀 uid
*/
public static final String UID_PRE = "uid:";
/**
* 存放用户信息uid
*/
public static final String USER_PRE = "user:";
/**
* 存放用户权限的前缀
*/
public static final String PERMISSION_PRE = "permission:";
/**
* 角色中的权限
*/
public static final String ROLE_PRE = "role:";
/**
* 字典缓存前缀
*/
public static final String DICT_PRE = "dict:";
/**
* 组织机构缓存前缀
*/
public static final String ORG_PRE = "org:";
/**
* 消息缓存前缀
*/
public static final String MESSAGE_PRE = "message:";
/**
* 组装key
*
* @param pre 前缀
* @param after 后缀
* @return key
*/
public static final String getKey(String pre, String after) {
return pre + after;
}
}
10、UserRealm.java实现
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import umbrella.tyrant.mq.MQUtils;
import umbrella.tyrant.shiro.UsernamePasswordRealmToken;
import umbrella.tyrant.util.StringUtils;
import yashiro.metagross.common.enumeration.Flag;
import yashiro.metagross.module.system.entity.User;
import yashiro.metagross.module.system.mq.UpdateUserLastLoginTimeCommand;
import yashiro.metagross.module.system.service.PrivService;
import yashiro.metagross.module.system.service.UserService;
import yashiro.metagross.module.system.util.PasswordUtils;
import yashiro.metagross.module.system.util.ShiroAuthorizationCacheManager;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* 使用User表作为用户主体的登录Realm
*/
public class UserRealm extends AuthorizingRealm implements Serializable {
private UserService userService;
private PrivService privService;
/**
* 认证回调函数,登录时调用
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
ShiroAuthorizationCacheManager.removeSession();
UsernamePasswordRealmToken token = (UsernamePasswordRealmToken) authenticationToken;
String username = token.getUsername();
String password = new String(token.getPassword());
User user = userService.findUserByUsername(username);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误");
}
String decodedPassword = Base64.decodeToString(password);
if (!user.getPassword().equals(PasswordUtils.encrypt(decodedPassword, user.getSalt()))) {
throw new UnknownAccountException("用户名或密码错误");
} else if (StringUtils.isBlank(user.getAvailableFlag()) || !user.getAvailableFlag().equals(Flag.YES.getCode())) {
throw new LockedAccountException("该用户已被禁用");
} else if (!user.getOrg().getAvailableFlag().equals(Flag.YES.getCode())) {
throw new LockedAccountException("该用户所属机构已被禁用");
}
ShiroAuthorizationCacheManager.setSession(user);
MQUtils.push(new UpdateUserLastLoginTimeCommand(user.getId(), new Date()));
return new SimpleAuthenticationInfo(user, password, getName());
}
/**
* 授权查询回调函数,进行权限判断但缓存中无用户的授权信息时调用
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
// (可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principalCollection);
SecurityUtils.getSubject().logout();
return null;
}
Object o = principalCollection.getPrimaryPrincipal();
if (!(o instanceof User))
return null;
User user = (User) o;
// 登录成功,查询全部的权限
List privList = privService.getPrivListByUserId(user.getId());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
HashSet set = new HashSet();
for (int i = 0; i < privList.size(); i++) {
Map priv = (Map) privList.get(i);
set.add(priv.get("code"));
}
//info.setRoles(set);
info.setStringPermissions(set);
return info;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setPrivService(PrivService privService) {
this.privService = privService;
}
}
学习文章:
https://blog.youkuaiyun.com/jrn1012/article/details/70373221
https://blog.youkuaiyun.com/zcyhappy1314/article/details/71270937