Springboot版本2.1.4
public enum LoginType {
/**
* 通用
*/
COMMON("common_realm"),
/**
* 用户密码登录
*/
USER_PASSWORD("user_password_realm"),
/**
* 手机验证码登录
*/
USER_PHONE("user_phone_realm");
private String type;
private LoginType(String type) {
this.type = type;
}
public String getType() {
return type;
}
@Override
public String toString() {
return this.type.toString();
}
}
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.UsernamePasswordToken;
public class ShiroToken extends UsernamePasswordToken {
private static final long serialVersionUID = 6358520744796868017L;
/**
* 登录方式
*/
private LoginType loginType;
/**
* 登录验证码
*/
private String code;
/**
* 保存请求的request
*/
private HttpServletRequest request;
public ShiroToken(LoginType loginType, String username, String password, String code) {
super(username, password);
this.loginType = loginType;
this.code = code;
this.setRememberMe(true); // 记住我的cookie生效
}
public ShiroToken(HttpServletRequest request, LoginType loginType, String username, String password, String code) {
super(username, password);
this.loginType = loginType;
this.code = code;
this.setRememberMe(true); // 记住我的cookie生效
this.request = request;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import wst.st.site.constant.constant.SiteWebConst;
import wst.st.site.shiro.LoginType;
import wst.st.site.shiro.override.MyCredentialsMatcher;
import wst.st.site.shiro.override.MyModularRealmAuthenticator;
import wst.st.site.shiro.override.MySessionManager;
import wst.st.site.shiro.override.MyShiroSessionListener;
import wst.st.site.shiro.realm.AuthorizationRealm;
import wst.st.site.shiro.realm.UserPasswordRealm;
import wst.st.site.shiro.realm.UserPhoneRealm;
import wst.st.site.shiro.realm.filter.AccessFormAuthenticationFilter;
import wst.st.site.shiro.realm.filter.KickoutAccessControlFilter;
/**
* shiro配置
* @author wst
*
*/
@Configuration
@ConfigurationProperties(prefix = "shiro")
public class ShiroConfiguration {
private static Logger log = LoggerFactory.getLogger(ShiroConfiguration.class);
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
/**
* 匿名请求过滤路径
*/
private String[] requestFilterUrlAnon;
/**
* 请求过滤路径
*/
private List<Map<String, String>> requestFilterUrlCustoms;
/**
* LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,
* 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。
* 主要是AuthorizingRealm类的子类,以及EhCacheManager类。
* 注:此方法需要用static作为修饰词,否则无法通过@Value()注解的方式获取配置文件的值
* @author wst 2019年4月12日 下午4:52:18
* @return
*/
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
log.info("ShiroConfiguration.getLifecycleBeanPostProcessor()");
return new LifecycleBeanPostProcessor();
}
/**
* 自定义的Realm管理,主要针对多realm
* @author wst 2019年4月12日 下午4:52:34
* @return
*/
@Bean
public MyModularRealmAuthenticator myModularRealmAuthenticator() {
MyModularRealmAuthenticator customizedModularRealmAuthenticator = new MyModularRealmAuthenticator();
// 设置realm判断条件
customizedModularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return customizedModularRealmAuthenticator;
}
/**
* 统一角色授权控制Realm
* @author wst 2019年4月12日 下午4:42:48
* @return
*/
@Bean
public AuthorizingRealm authorizingRealm() {
AuthorizationRealm authorizationRealm = new AuthorizationRealm();
authorizationRealm.setName(LoginType.COMMON.getType());
return authorizationRealm;
}
/**
* 密码登录realm
* @author wst 2019年4月12日 下午4:52:49
* @return
*/
@Bean
public UserPasswordRealm userPasswordRealm() {
UserPasswordRealm userPasswordRealm = new UserPasswordRealm();
userPasswordRealm.setName(LoginType.USER_PASSWORD.getType());
// 自定义的密码校验器
userPasswordRealm.setCredentialsMatcher(credentialsMatcher());
return userPasswordRealm;
}
/**
* 手机号验证码登录realm
* @author wst 2019年4月12日 下午4:52:57
* @return
*/
@Bean
public UserPhoneRealm userPhoneRealm() {
UserPhoneRealm userPhoneRealm = new UserPhoneRealm();
userPhoneRealm.setName(LoginType.USER_PHONE.getType());
return userPhoneRealm;
}
/**
* SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类
* @author wst 2019年4月12日 下午4:53:05
* @return
*/
@Bean
public SecurityManager securityManager() {
log.info("ShiroConfiguration.getDefaultWebSecurityManager()");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setAuthenticator(myModularRealmAuthenticator());
List<Realm> realms = new ArrayList<>();
// 统一角色权限控制realm
realms.add(authorizingRealm());
// 用户密码登录realm
realms.add(userPasswordRealm());
// 用户手机号验证码登录realm
realms.add(userPhoneRealm());
securityManager.setRealms(realms);
// 用户授权/认证信息Cache, 采用EhCache 缓存
// securityManager.setCacheManager(ehCacheManager());
// 注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(redisCacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter
* 它主要保存了三项数据,securityManager,filters,filterChainDefinitionManager
* @author wst 2019年4月12日 下午4:53:16
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter() {
log.info("ShiroConfiguration.shiroFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
// 未授权跳转界面;
// shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
// shiroFilterFactoryBean.setLoginUrl("/account/login");
// 登录成功后要跳转的链接
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 过滤器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 可以匿名请求的url 静态资源
if(requestFilterUrlAnon != null){
for(int i = 0; i < requestFilterUrlAnon.length; i ++){
log.info("anon --> {}", requestFilterUrlAnon[i]);
filterChainDefinitionMap.put(requestFilterUrlAnon[i], "anon");
}
}
// 需过滤的url
if(requestFilterUrlCustoms != null){
for(int i = 0; i < requestFilterUrlCustoms.size(); i ++){
log.info(requestFilterUrlCustoms.get(i).get("filter") + " --> {}", requestFilterUrlCustoms.get(i).get("url"));
filterChainDefinitionMap.put(requestFilterUrlCustoms.get(i).get("url"), requestFilterUrlCustoms.get(i).get("filter"));
}
}
// filterChainDefinitionMap.put("/account/logout", "logout");
// 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// filterChainDefinitionMap.put("/**", "authc");
// 加载shiroFilter权限控制规则
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 自定义过滤器
Map<String, Filter> filter = shiroFilterFactoryBean.getFilters();
// 限制同一帐号同时在线的个数。
filter.put("kickout", kickoutSessionControlFilter());
// 登陆验证
filter.put("authc", new AccessFormAuthenticationFilter());
// 此处需要添加一个kickout,上面添加的自定义拦截器才能生效
// filterChainDefinitionMap.put("/account/login", "kickout");// 表示需要认证才可以访问
return shiroFilterFactoryBean;
// rest: 例如/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。
// port:例如/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
// perms:例如/admins/user/**=perms[user:add:*],perms参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
// roles:例如/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如/admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
// anon: 例如/admins/**=anon 没有参数,表示可以匿名使用。
// authc: 例如/admins/user/**=authc表示需要认证才能使用,没有参数。
// authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证。
// ssl: 例如/admins/user/**=ssl没有参数,表示安全的url请求,协议为https。
// user: 例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查。
//
// 这些过滤器分为两组,一组是认证过滤器,一组是授权过滤器。其中anon,authcBasic,auchc,user是第一组,perms,roles,ssl,rest,port是第二组。
//
// 注释支持
// @RequiresAuthentication 验证用户是否登录,等同于方法subject.isAuthenticated()
// 结果为true时;
// @RequiresUser
// 验证用户是否被记忆,user有两种含义:一种是成功登录的(subject.isAuthenticated()结果为true)另外一种是被记忆的(subject.isRemembered()结果为true);
// @RequiresGuest 验证是否为匿名请求;
// @RequiresRoles 必须要有角色;
// @RequiresPermissions 必须要有权限;
// 1、一个URL可以配置多个 Filter,使用逗号分隔
// 2、当设置多个过滤器时,全部验证通过,才视为通过
// 3、部分过滤器可指定参数,如 perms,roles
}
/**
* 自定义密码校验
* @author wst 2019年4月12日 上午11:46:06
* @return
*/
@Bean
public MyCredentialsMatcher credentialsMatcher() {
return new MyCredentialsMatcher();
}
/**
* 授权所用配置
*
* @author wst 2019年4月12日 下午4:53:56
* @return
*/
@Bean
@DependsOn({ "lifecycleBeanPostProcessor" })
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 使授权注解起作用不如不想配置可以在pom文件中加入 <dependency>
* <groupId>org.springframework.boot</groupId>
* <artifactId>spring-boot-starter-aop</artifactId> </dependency>
*
* @author wst 2019年4月12日 下午4:54:06
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
log.info("ShiroConfiguration.authorizationAttributeSourceAdvisor()");
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
/**
* 限制同一账号登录同时登录人数控制
* @author wst 2019年4月12日 下午4:54:15
* @return
*/
@Bean
public KickoutAccessControlFilter kickoutSessionControlFilter() {
KickoutAccessControlFilter kickoutSessionControlFilter = new KickoutAccessControlFilter();
kickoutSessionControlFilter.setCache(redisCacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setMaxSession(SiteWebConst.Shiro.Config.Login_max_session);
kickoutSessionControlFilter.setKickoutUrl(SiteWebConst.Shiro.Config.Login_kickout_url);
return kickoutSessionControlFilter;
}
/**
* EhCacheManager 缓存管理 ehCache实现
* @author wst 2019年4月12日 下午4:54:23
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public EhCacheManager ehCacheManager() {
log.info("ShiroConfiguration.ehCacheManager()");
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
}
/**
* 配置RedisCacheManager 缓存管理
* @author wst 2019年4月12日 下午4:54:32
* @return
*/
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setPrincipalIdFieldName("id");
redisCacheManager.setKeyPrefix(SiteWebConst.Shiro.Key.Shiro_redis_cache_key_prefix); // 设置前缀
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现
* @author wst 2019年4月12日 下午4:54:42
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setKeyPrefix(SiteWebConst.Shiro.Key.Shiro_redis_session_key_prefix);
return redisSessionDAO;
}
/**
* SessionManager
* @author wst 2019年4月12日 下午4:54:52
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public MySessionManager sessionManager() {
MySessionManager sessionManager = new MySessionManager();
Collection<SessionListener> listeners = new ArrayList<>();
// 保存sessionId的cookie
SimpleCookie sessionIdCookie = new SimpleCookie();
sessionIdCookie.setMaxAge(SiteWebConst.Cookies.Config.Time_out);
sessionIdCookie.setName(SiteWebConst.Cookies.Key.Shiro_session_id_cookie);
sessionIdCookie.setPath("/");
sessionIdCookie.setHttpOnly(true);
sessionManager.setSessionIdCookie(sessionIdCookie);
listeners.add(new MyShiroSessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(redisSessionDAO());
//url中是否显示session Id
sessionManager.setSessionIdUrlRewritingEnabled(false);
// 删除失效的session
sessionManager.setDeleteInvalidSessions(true);
// 是否在cookie中获取sessionId
sessionManager.setSessionIdCookieEnabled(true);
// session 有效时间
sessionManager.setGlobalSessionTimeout(SiteWebConst.Shiro.Config.Session_time_out); // 毫秒
return sessionManager;
}
/**
* 配置shiro-redis
* @author wst 2019年4月12日 下午4:55:00
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisHost);
redisManager.setPort(redisPort);
redisManager.setTimeout(1800); // 设置过期时间
redisManager.setPassword(redisPassword);
return redisManager;
}
/**
* 记住我Cookie
* remenberMeCookie是一个实现了将用户名保存在客户端的一个cookie,与登陆时的cookie是两个simpleCookie。
* 登陆时会根据权限去匹配,如是user权限,则不会先去认证模块认证,而是先去搜索cookie中是否有rememberMeCookie,
* 如果存在该cookie,则可以绕过认证模块,直接寻找授权模块获取角色权限信息。
* 如果权限是authc,则仍会跳转到登陆页面去进行登陆认证.
* @author 2019年4月11日 下午8:50:53
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
log.info("ShiroConfiguration.rememberMeCookie()");
// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie();
// 记住我cookie生效时间,单位秒
simpleCookie.setMaxAge(SiteWebConst.Cookies.Config.Time_out);
simpleCookie.setName("rememberMe");
simpleCookie.setPath("/");
simpleCookie.setHttpOnly(true);
return simpleCookie;
}
/**
* cookie管理对象;记住我Cookie
* @author wst 2019年4月12日 下午4:55:10
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
log.info("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
*
* @author wst 2019年4月12日 下午4:55:16
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
log.info("ShiroConfiguration.shiroDialect()");
return new ShiroDialect();
}
public String[] getRequestFilterUrlAnon() {
return requestFilterUrlAnon;
}
public void setRequestFilterUrlAnon(String[] requestFilterUrlAnon) {
this.requestFilterUrlAnon = requestFilterUrlAnon;
}
public List<Map<String, String>> getRequestFilterUrlCustoms() {
return requestFilterUrlCustoms;
}
public void setRequestFilterUrlCustoms(List<Map<String, String>> requestFilterUrlCustoms) {
this.requestFilterUrlCustoms = requestFilterUrlCustoms;
}
}
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wst.st.site.constant.enums.ResponseCodeEnum;
import wst.st.site.constant.message.SiteResponseMessage;
import wst.st.site.data.entity.SiteAccount;
import wst.st.site.exception.SiteShiroAuthenticationException;
import wst.st.site.shiro.ShiroToken;
import wst.st.site.tools.MD5Util;
/**
* 自定义密码校验
* @author wst
*
*/
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
Logger log = LoggerFactory.getLogger(MyCredentialsMatcher.class);
/**
* 所需要的信息如何得到可以在wst.st.site.shiro.realm.UserPasswordRealm类中找到
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
ShiroToken stoken = (ShiroToken) token;
log.info("LoginType --------------------> {}", stoken.getLoginType().getType());
// 根据不同realm使用不同的校验方式
switch(stoken.getLoginType()){
case USER_PHONE:
break;
case USER_PASSWORD:
// 获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String inPassword = new String(stoken.getPassword());
// 获得数据库中的密码
SiteAccount account = (SiteAccount) info.getPrincipals().getPrimaryPrincipal();
String dbPassword = (String) info.getCredentials();
// 进行密码的比对
// 如果是手机登录,传递过来的是已经加密的验证码,只需要把用户输入的验证码加密进行校验即可
String md5pwd = MD5Util.md5ByDynamicSalt2(inPassword, account.getSalt() + account.getStr()); // MD5加盐加密
if(!dbPassword.equals(md5pwd)){
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.USERNAME_PASSWORD_ERROR);
}
break;
case COMMON:
default:
throw new SiteShiroAuthenticationException(ResponseCodeEnum.SHIRO_ERROR.getDesc());
}
return Boolean.TRUE;
}
}
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import wst.st.site.shiro.LoginType;
import wst.st.site.shiro.ShiroToken;
import java.util.Collection;
import java.util.HashMap;
/**
* 自定义多realm登录策略
* @author wst
*
*/
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
for (Realm realm : realms) {
realmHashMap.put(realm.getName(), realm);
}
ShiroToken token = (ShiroToken) authenticationToken;
// 登录类型
LoginType loginType = token.getLoginType();
if (realmHashMap.get(loginType.getType()) != null) {
return doSingleRealmAuthentication(realmHashMap.get(loginType.getType()), token);
} else {
return doMultiRealmAuthentication(realms, token);
}
}
}
import java.io.Serializable;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
//import org.apache.shiro.session.Session;
//import org.apache.shiro.session.UnknownSessionException;
//import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
//import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import wst.st.site.constant.constant.SiteWebConst;
/**
* 自定义sessionId获取
* Shiro的sessionId只有在调用Session session = SecurityUtils.getSubject().getSession(); 的时候才会生成sessionId
* 然后通过写cookie的方式写到浏览器
* @author wst
*
*/
public class MySessionManager extends DefaultWebSessionManager {
private Logger log = LoggerFactory.getLogger(MySessionManager.class);
private static final String AUTHORIZATION = SiteWebConst.Shiro.Config.SessionId_authorization;
private static final String REFERENCED_SESSION_ID_SOURCE = SiteWebConst.Shiro.Config.Referenced_session_id_source;
public MySessionManager() {
super();
setGlobalSessionTimeout(SiteWebConst.Shiro.Config.Session_time_out);
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// ajax请求 获取请求头,或者请求参数中的Token
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// String id = StringUtils.isEmpty(WebUtils.toHttp(request).getHeader(AUTHORIZATION)) ? request.getParameter(AUTHORIZATION) : WebUtils.toHttp(request).getHeader(AUTHORIZATION);
log.info("sessionId ----------> {}", sessionId);
// 如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(sessionId) && !"undefined".equalsIgnoreCase(sessionId)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
// 否则按默认规则从cookie取sessionId
sessionId = (String) super.getSessionId(request, response);
return sessionId;
}
}
/**
* 获取session 优化单次请求需要多次访问redis的问题
*
* @param sessionKey
* @return
* @throws UnknownSessionException
*/
// @Override
// protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
// Serializable sessionId = getSessionId(sessionKey);
//
// ServletRequest request = null;
// if (sessionKey instanceof WebSessionKey) {
// request = ((WebSessionKey) sessionKey).getServletRequest();
// }
//
// if (request != null && null != sessionId) {
// Object sessionObj = request.getAttribute(sessionId.toString());
// if (sessionObj != null) {
// return (Session) sessionObj;
// }
// }
//
// Session session = super.retrieveSession(sessionKey);
// if (request != null && null != sessionId) {
// request.setAttribute(sessionId.toString(), session);
// }
// return session;
// }
}
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
public class MyShiroSessionListener implements SessionListener{
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.incrementAndGet();
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
}
}
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wst.st.site.clazz.response.SiteLoginUserInfor;
/**
* 统一角色授权控制Realm
* @author wst
*
*/
public class AuthorizationRealm extends AuthorizingRealm {
private static Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
/**
* 设置realm的名称
*/
@Override
public String getName() {
return LoginType.COMMON.getType();
}
/**
* 授权用户权限
* 只有在成功登录后,当需要检测用户权限的时候才会调用此方法,如方法标注注解@RequiresPermissions("user:list")、@RequiresRoles("guest")
* Shiro 的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
* 当访问到页面的时候,链接配置了相应的权限或者 Shiro 标签才会执行此方法否则不会执行。
* 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回 null 即可。
* 在这个方法中主要是使用类:SimpleAuthorizationInfo进行角色的添加和权限的添加。
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("##################执行Shiro权限认证##################");
Object principal = principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if (principal instanceof SiteLoginUserInfor) {
SiteLoginUserInfor account = (SiteLoginUserInfor) principal;
if(account != null){
info.addStringPermission("设置权限");
info.addRole("设置角色");
}
}
return info;
}
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException{
return null;
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
import wst.st.site.clazz.response.ServerResponse;
import wst.st.site.clazz.response.SiteLoginUserInfor;
import wst.st.site.constant.constant.RedisKeyConstant;
import wst.st.site.constant.message.SiteResponseMessage;
import wst.st.site.data.dao.StSiteAccountMapper;
import wst.st.site.exception.SiteShiroAuthenticationException;
import wst.st.site.redis.Jedis.RedisDatabaseConstants;
import wst.st.site.redis.Jedis.RedisUtil;
import wst.st.site.service.IUserInfoService;
import wst.st.site.shiro.LoginType;
import wst.st.site.shiro.ShiroToken;
import wst.st.site.tools.StringUtils;
/**
* 用户密码登录realm
*/
@Slf4j
public class UserPasswordRealm extends AuthorizingRealm {
private static Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
@Autowired
private StSiteAccountMapper stSiteAccountMapper;
@Autowired
private IUserInfoService userInfoService;
@Autowired
private RedisUtil redis;
/**
* 设置realm的名称
*/
@Override
public String getName() {
return LoginType.USER_PASSWORD.getType();
}
@Override
public boolean supports(AuthenticationToken token) {
if (token instanceof ShiroToken) {
return ((ShiroToken) token).getLoginType() == LoginType.USER_PASSWORD;
} else {
return false;
}
}
@Override
public void setAuthorizationCacheName(String authorizationCacheName) {
super.setAuthorizationCacheName(authorizationCacheName);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 验证用户身份
* 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("##################执行Shiro身份认证##################");
ShiroToken token = (ShiroToken) authenticationToken;
// 获取shiro的Session 获取session中保存的验证码
// Session session = SecurityUtils.getSubject().getSession();
// String severAuthCode = (String) session.getAttribute(SiteWebConst.Shiro.Key.Auth_code_session_key);
// 获取保存在redis中的验证码
String severAuthCode = redis.get(RedisKeyConstant.getAuth_code_key(token.getRequest()), RedisDatabaseConstants.DATEBASE_1);
// 用户输入的验证码
String clientAuthCode = token.getCode();
if(StringUtils.isBlank(clientAuthCode)) {
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.VERIFICATION_CODE_ERROR);
}
if(StringUtils.isBlank(clientAuthCode) || !clientAuthCode.equalsIgnoreCase(severAuthCode)) {
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.VERIFICATION_CODE_ERROR);
}
// 验证成功后删除session中保存的验证码, 也可以不删除,时间到了自动删除
// session.remove?Attribute(SiteWebConst.Shiro.Key.Auth_code_session_key);
redis.del(RedisDatabaseConstants.DATEBASE_1, RedisKeyConstant.getAuth_code_key(token.getRequest()));
// 查询数据库是否存在
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
SiteLoginUserInfor account = stSiteAccountMapper.getAccountByAccount(token.getUsername());
if(account == null){
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.USERNAME_NOT_EXISTS);
}
// 用户为禁用状态
if (account.getStstus() != 1) {
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.USERNAME_FORBIDDEN);
}
// 获取昵称
ServerResponse<String> nicknameData = userInfoService.getSiteUserNickname(account.getUserId());
if(nicknameData.getStatus() == 0){
account.setNickname(nicknameData.getData());
}
// 设置sessionId
account.setSessionId(SecurityUtils.getSubject().getSession().getId().toString());
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
account, //用户
account.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
import wst.st.site.clazz.response.ServerResponse;
import wst.st.site.clazz.response.SiteLoginUserInfor;
import wst.st.site.constant.constant.SiteWebConst;
import wst.st.site.constant.message.SiteResponseMessage;
import wst.st.site.data.dao.StSiteAccountMapper;
import wst.st.site.exception.SiteShiroAuthenticationException;
import wst.st.site.service.IUserInfoService;
import wst.st.site.shiro.LoginType;
import wst.st.site.shiro.ShiroToken;
import wst.st.site.tools.MD5Util;
/**
* 手机验证码登录realm
*/
@Slf4j
public class UserPhoneRealm extends AuthorizingRealm {
private static Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
@Autowired
private StSiteAccountMapper stSiteAccountMapper;
@Autowired
private IUserInfoService userInfoService;
/**
* 设置realm的名称
*/
@Override
public String getName() {
return LoginType.USER_PHONE.getType();
}
@Override
public boolean supports(AuthenticationToken token) {
if (token instanceof ShiroToken) {
return ((ShiroToken) token).getLoginType() == LoginType.USER_PHONE;
} else {
return false;
}
}
@Override
public void setAuthorizationCacheName(String authorizationCacheName) {
super.setAuthorizationCacheName(authorizationCacheName);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
log.info("##################执行Shiro身份认证##################");
ShiroToken token = (ShiroToken) authcToken;
// session 保存的验证码
String severAuthCode = (String) SecurityUtils.getSubject().getSession().getAttribute(SiteWebConst.Shiro.Key.Auth_code_session_key);
// 用户输入的验证码
String clientAuthCode = token.getCode();
if(StringUtils.isBlank(clientAuthCode)) {
throw new SiteShiroAuthenticationException("验证码不能为空!");
}
if(!clientAuthCode.equalsIgnoreCase(severAuthCode)) {
throw new SiteShiroAuthenticationException("验证码错误,请重新输入!");
}
// 查询数据库是否存在
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
// MyCredentialsMatcher里进行了账号密码登录的校验,判断的用户输入的密码是要加密的,而这里传递过去的是验证码,
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
SiteLoginUserInfor account = stSiteAccountMapper.getAccountByAccount(token.getUsername());
// 用户名不存在
if(account == null){
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.USERNAME_NOT_EXISTS);
}
// 用户名为禁用状态
if (account.getStstus() != 1) {
throw new SiteShiroAuthenticationException(SiteResponseMessage.Auth.USERNAME_FORBIDDEN);
}
// 获取昵称
ServerResponse<String> nicknameData = userInfoService.getSiteUserNickname(account.getUserId());
if(nicknameData.getStatus() == 0){
account.setNickname(nicknameData.getData());
}
// 设置sessionId
account.setSessionId(SecurityUtils.getSubject().getSession().getId().toString());
// 因此这里把验证码加密一遍, 然后在MyCredentialsMatcher与加密后的用户输入的验证码进行校验
String md5AuthCode = MD5Util.md5ByDynamicSalt2(severAuthCode, account.getSalt() + account.getStr()); // MD5加盐加密
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
account, //用户
md5AuthCode, // 验证码
getName() //realm name
);
return authenticationInfo;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
import java.io.PrintWriter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wst.st.site.clazz.response.ServerResponse;
import wst.st.site.constant.constant.SiteWebConst;
import wst.st.site.constant.message.SiteResponseMessage;
import wst.st.site.tools.JsonUtil;
/**
* 登录认证 对请求进行校验,如果没有通过校验的会跳转到指定的loginUrl(默认/login.jsp,可以自定义)
* 重写方法是为了返回json而不是默认的重定向
* @author wst
*
*/
public class AccessFormAuthenticationFilter extends FormAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(AccessFormAuthenticationFilter.class);
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//Always return true if the request's method is OPTIONS
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return Boolean.TRUE;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
/**
* 未登录或无权限处理
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest)request;
log.info("未登录或者无权限处理 ---- {}", req.getRequestURI());
if(isAjax(request)){
}
HttpServletResponse resp = (HttpServletResponse) response;
// 设置响应头允许跨越响应
resp.addHeader(SiteWebConst.Common.Header.Access_control_allow_origin, req.getHeader(SiteWebConst.Common.Header.Access_control_allow_origin));
resp.addHeader(SiteWebConst.Common.Header.Access_control_allow_credentials, req.getHeader(SiteWebConst.Common.Header.Access_control_allow_credentials));
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json; charset=utf-8");
PrintWriter out = resp.getWriter();
out.print(JsonUtil.toJson(ServerResponse.createByErrorMessage(SiteResponseMessage.Auth.NOT_LOGIN_OR_EXPIRE))); //"url已过期"
out.flush();
out.close();
return Boolean.FALSE;
}
/**
* 是否ajax请求
* @author wst 2019年4月13日 下午4:56:59
* @param request
* @return
*/
private boolean isAjax(ServletRequest request){
String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
if("XMLHttpRequest".equalsIgnoreCase(header)){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import com.alibaba.fastjson.JSON;
import wst.st.site.clazz.response.SiteLoginUserInfor;
import wst.st.site.constant.constant.SiteWebConst;
/**
* 自定义拦截器,用于限制用户登录人数
* @author wst
*
*/
public class KickoutAccessControlFilter extends AccessControlFilter {
private String kickoutUrl; //踢出后到的地址
private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
private int maxSession = 1; //同一个帐号最大会话数 默认1
private SessionManager sessionManager;
private Cache<String, Deque<Serializable>> cache;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 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();
// 获取到登录的账号
// TODO 在eureka微服务中 这里强转时会报错,去除热服务的jar依赖后正常,可能与热部署有关
SiteLoginUserInfor account = (SiteLoginUserInfor) subject.getPrincipal();
String username = account.getAccount();
Serializable sessionId = session.getId();
//读取缓存 没有就存入
Deque<Serializable> deque = cache.get(username);
//如果此用户没有session队列,也就是还没有登录过,缓存中没有
//就new一个空队列,不然deque对象为空,会报空指针
if(deque == null){
deque = new LinkedList<Serializable>();
}
//如果队列里没有此sessionId,且用户没有被踢出;放入队列
if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
//将sessionId存入队列
deque.push(sessionId);
//将用户的sessionId队列缓存
cache.put(username, deque);
}
//如果队列里的sessionId数超出最大会话数,开始踢人
while(deque.size() > maxSession) {
Serializable kickoutSessionId = null;
if(kickoutAfter) { //如果踢出后者
kickoutSessionId = deque.removeFirst();
//踢出后再更新下缓存队列
cache.put(username, deque);
} else { //否则踢出前者
kickoutSessionId = deque.removeLast();
//踢出后再更新下缓存队列
cache.put(username, deque);
}
try {
//获取被踢出的sessionId的session对象
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if(kickoutSession != null) {
//设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
} catch (Exception e) {//ignore exception
}
}
//如果被踢出了,直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null) {
//会话被踢出了
try {
//退出登录
subject.logout();
} catch (Exception e) { //ignore
}
saveRequest(request);
Map<String, String> resultMap = new HashMap<String, String>();
//判断是不是Ajax请求
if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
resultMap.put("user_status", "300");
resultMap.put("message", "您已经在其他地方登录,请重新登录!");
//输出json串
out(response, resultMap);
}else{
//重定向
WebUtils.issueRedirect(request, response, kickoutUrl);
}
return false;
}
return true;
}
private void out(ServletResponse hresponse, Map<String, String> resultMap)
throws IOException {
try {
hresponse.setCharacterEncoding("UTF-8");
PrintWriter out = hresponse.getWriter();
out.println(JSON.toJSONString(resultMap));
out.flush();
out.close();
} catch (Exception e) {
System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。");
}
}
public String getKickoutUrl() {
return kickoutUrl;
}
public void setKickoutUrl(String kickoutUrl) {
this.kickoutUrl = kickoutUrl;
}
public boolean isKickoutAfter() {
return kickoutAfter;
}
public void setKickoutAfter(boolean kickoutAfter) {
this.kickoutAfter = kickoutAfter;
}
public int getMaxSession() {
return maxSession;
}
public void setMaxSession(int maxSession) {
this.maxSession = maxSession;
}
public SessionManager getSessionManager() {
return sessionManager;
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public Cache<String, Deque<Serializable>> getCache() {
return cache;
}
public void setCache(CacheManager cacheManager) {
this.cache = cacheManager.getCache(SiteWebConst.Shiro.Key.Shiro_redis_cache_key_prefix);
}
}
/**
* 登陆
* @author wst 2018年10月16日 下午10:17:44
* @param account 账号
* @param password 密码
* @param code 验证码
* @return
*/
@ResponseBody
@RequestMapping(value = "login", method = RequestMethod.POST)
ServerResponse<SiteAccount> login(HttpServletRequest request, String account, String password, String code){
ShiroToken shiroToken = new ShiroToken(request, LoginType.USER_PASSWORD, account, password, code);
//登录不在该处处理,交由shiro处理
Subject subject = SecurityUtils.getSubject();
subject.login(shiroToken);
if (subject.isAuthenticated()) {
String sessionId = (String) subject.getSession().getId();
SiteLoginUserInfor userInfo = (SiteLoginUserInfor) subject.getPrincipal();
userInfo.setSalt(null);
userInfo.setStr(null);
userInfo.setPassword(null);
userInfo.setSessionId(sessionId);
// 发送日志
queueProducer.sendLogMessage(SiteWebConst.Common.QueueMessageType.Message_log_opertion,
SiteWebConst.Common.OperateAction.login, userInfo.getUserId().toString(), null, null);
return ServerResponse.createBySuccess(SiteResponseMessage.Auth.LOGIN_SUCCESS, userInfo);
}else{
return ServerResponse.createByErrorMessage(ResponseCodeEnum.SHIRO_ERROR.getDesc());
}
}
/**
* 退出
* @author wst 2018年10月16日 下午10:17:44
* @param account 账号
* @param password 密码
* @return
*/
@ResponseBody
@RequestMapping(value = "logout", method = RequestMethod.POST)
ServerResponse<String> logOut(){
SecurityUtils.getSubject().logout();
return ServerResponse.createBySuccess(SiteResponseMessage.Auth.LOGOUT_SUCCESS, SiteWebConst.Cookies.Key.Shiro_session_id_cookie);
}
/**
* 获取shiro sessionId
* @author wst 2019年4月18日 下午1:54:37
* @return
*/
@ResponseBody
@RequestMapping(value = "getShiroSessionId", method = RequestMethod.POST)
ServerResponse<Map<String, Object>> getShiroSessionId(){
try {
Subject subject = SecurityUtils.getSubject();
String sessionId = (String) subject.getSession().getId();
Map<String, Object> backData = new HashMap<String, Object>();
Map<String, String> sessionMap = new HashMap<String, String>();
sessionMap.put("_key", SiteWebConst.Cookies.Key.Shiro_session_id_cookie);
sessionMap.put("_value", sessionId);
System.out.println( "sessionId -- >" + sessionId);
SiteWebConst.setCookie(response, "99999", "testCookieAndHeaders");
Cookie[] cookies = SiteWebConst.getCookieArray(request);
if(cookies != null){
for(int i = 0; i < cookies.length; i ++){
System.out.println( "cookies -- >" + cookies[i].getValue());
}
}
System.out.println( "header -- >" + request.getHeader(SiteWebConst.Shiro.Config.SessionId_authorization));
// 获取登陆的用户信息
SiteLoginUserInfor user = (SiteLoginUserInfor) subject.getPrincipal();
// 从redis中获取访问数量
String visit = redis.get(RedisKeyConstant.Site_visit_number_key, RedisDatabaseConstants.DATEBASE_1);
backData.put("sessionMap", sessionMap);
backData.put("loginMap", user);
backData.put("visit", visit);
return ServerResponse.createBySuccess(SiteResponseMessage.Common.GET_DATA_SUCCESS, backData);
} catch (Exception e) {
e.printStackTrace();
return ServerResponse.createByErrorMessage(SiteResponseMessage.Common.GET_DATA_FAIL);
}
}
import java.io.Serializable;
import java.util.Date;
public class SiteAccount implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5290258498172598335L;
public SiteAccount(){}
protected Integer id;
protected Integer userId;
protected String account;
protected String password;
protected String salt;
protected String str;
protected Integer ststus;
protected Integer privilege;
protected Date updateTime;
protected Date createTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account == null ? null : account.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt == null ? null : salt.trim();
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str == null ? null : str.trim();
}
public Integer getStstus() {
return ststus;
}
public void setStstus(Integer ststus) {
this.ststus = ststus;
}
public Integer getPrivilege() {
return privilege;
}
public void setPrivilege(Integer privilege) {
this.privilege = privilege;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
import java.io.Serializable;
import wst.st.site.data.entity.SiteAccount;
/**
* 用户登录成功后的信息
* @author wst
*
*/
public class SiteLoginUserInfor extends SiteAccount implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7029736560050494826L;
/**
* sessionId
*/
private String sessionId;
/**
* 用户昵称
*/
private String nickname;
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
// TODO 作为保存到shiro 里的Principal 继承的父类和子类都需要实现Serializable, 以及子类需要有如下方法,否在报错
// authCacheKey or id
// We need a field to identify this Cache Object in Redis. So you need to defined an id field which you can get unique id to identify this principal.
// For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. For example, getUserId(), getUserName(), getEmail(), etc.
// Default value is authCacheKey or id, that means your principal object has a method called "getAuthCacheKey()" or "getId()"
@Override
public Integer getId() {
return super.getId();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">
<diskStore path="java.io.tmpdir"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:
Ehcache的三种清空策略;
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!-- 登录记录缓存锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>