版权声明:严禁用于任何商业用途的转发!
注意:
1.这是一篇探讨性的文章,并不能作为指导性内容
2.具体的shiro认证流程等不做说明
3.在此之前,我尝试过利用shiro的ThreadLocal、数据库持久化等等手段,均没有达到理想中的效果!特别是ThreadLocal,存储后不论我怎么修改代码,一直获取不到写入的信息,有清楚原因的大大烦请指导一下!
业务需求:
1.多组织下实现用户组织认证登录
2.登录后任意请求获取当前登录组织信息
3.退出登录时,清空组织信息
实现思路:
最初的时候,我是将组织信息持久化保存到user表中,实际使用中效率以及效果并不理想,
下面代码示例中的思路是:在shiro config中初始化LoginOrgManager(自定义登录组织管理器类),在FormAuthenticationFilter中将登录组织信息写入LoginOrgManager.resources
实现代码:
1.自定义Token:
package org.bluedream.core.config.shiro;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* @ClassName CustomUserNamePasswordToken
* @Description TODO
* @Author foxsand
* @Data 2021-06-18 11:25
* @Version
*/
@Data
@NoArgsConstructor
public class CustomUsernamePasswordToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1560242330091698700L;
private String orgCode;
public CustomUsernamePasswordToken(String username, String password,
boolean rememberMe, String host, String orgCode) {
super(username, password, rememberMe, host);
this.orgCode = orgCode;
}
}
2.自定义登录表单过滤器:
package org.bluedream.core.config.shiro;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.bluedream.comm.utils.EmptyUtil;
import org.bluedream.core.config.shiro.loginOrganization.LoginOrgManager;
import org.bluedream.core.config.shiro.loginOrganization.LoginUserRealm;
import org.bluedream.core.utils.YmlRead;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
private String orgCodeParam="orgCode";
private LoginOrgManager loginOrgManager;
public LoginOrgManager getLoginOrgManager() {
if (EmptyUtil.isEmpty(this.loginOrgManager)){
this.loginOrgManager = new LoginOrgManager();
}
return loginOrgManager;
}
public void setLoginOrgManager(LoginOrgManager loginOrgManager) {
this.loginOrgManager = loginOrgManager;
}
public String getOrgCodeParam() {
return orgCodeParam;
}
public void setOrgCodeParam(String orgCodeParam){
this.orgCodeParam = orgCodeParam;
}
protected String getOrgCode(ServletRequest request) {
return WebUtils.getCleanParam(request, getOrgCodeParam());
}
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String orgCode = getOrgCode(request);
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return new CustomUsernamePasswordToken(username, password, rememberMe,
host, orgCode);
}
/**
* 登录成功后 将组织信息写入 LoginOrgManager.resources
* @param token
* @param subject
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
//todo:将登陆组织信息赋值给LoginUserRealm,然后存入LoginOrgManager
LoginOrgManager loginOrgManager = getLoginOrgManager();
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
CustomUsernamePasswordToken authenticationToken = (CustomUsernamePasswordToken) token;
session.setAttribute(LoginOrgManager.LOGIN_ORG_MANAGER_KEY , authenticationToken.getOrgCode());
session.setAttribute(LoginOrgManager.LOGIN_ORG_SUBJECT_SESSION_ID , subject.getSession().getId());
LoginUserRealm loginUser = new LoginUserRealm(subject , authenticationToken.getOrgCode() , session);
loginOrgManager.put((String) subject.getSession().getId() , loginUser);
return super.onLoginSuccess(token, subject, request, response);
}
}
3.LoginUserRealm(注意:这里我是继承了一个User类,非必要):
package org.bluedream.core.config.shiro.loginOrganization;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.shiro.subject.Subject;
import org.bluedream.core.module.sys.entity.User;
import javax.servlet.http.HttpSession;
/**
* @ClassName LoginUserRealm
* @Description TODO
* @Author Administrator
* @Data 2022/5/20 16:33
* @Version
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString
public class LoginUserRealm extends User {
private static final long serialVersionUID = -5835809290701243876L;
private Subject subject;
private HttpSession loginOrgSession;
public LoginUserRealm(Subject subject, String orgCode, HttpSession loginOrgSession) {
this.subject = subject;
setOrgCode(orgCode);
this.loginOrgSession = loginOrgSession;
}
public LoginUserRealm(Subject subject) {
this.subject = subject;
}
public LoginUserRealm(String orgCode) {
this.orgCode = orgCode;
}
public LoginUserRealm(HttpSession loginOrgSession) {
this.loginOrgSession = loginOrgSession;
}
}
4.登录组织管理器:
package org.bluedream.core.config.shiro.loginOrganization;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName LoginOrgManager 登陆组织管理器
* @Description TODO
* @Author Administrator
* @Data 2022/5/20 11:49
* @Version
*/
@Data
@NoArgsConstructor
public class LoginOrgManager{
private static final long serialVersionUID = -3434414553744010746L;
public static final String LOGIN_ORG_MANAGER_KEY = LoginOrgManager.class.getName() + "_LOGIN_ORG_MANAGER_KEY";
public static final String LOGIN_ORG_SUBJECT_SESSION_ID = LoginOrgManager.class.getName() + "_LOGIN_ORG_SUBJECT_SESSION_ID";
private static final Map resources = new HashMap<>();
public Map getResources() {
return resources;
}
public Object getValue(String key){
return resources != null ? resources.get(key) : null;
}
public Object get(String key){
return getValue(key);
}
public void put(String key, Object value){
if (key == null){
throw new IllegalArgumentException("key cannot be null");
}else if (value == null){
remove(key);
}else {
Object obj = get(key);
if (obj != null){
remove(key);
}
resources.put(key , value);
}
}
public void remove(String key){
resources.remove(key);
}
}
5.Logout过滤器:
package org.bluedream.core.config.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.bluedream.comm.utils.EmptyUtil;
import org.bluedream.core.config.shiro.loginOrganization.LoginOrgManager;
import org.bluedream.core.config.shiro.loginOrganization.LoginUserRealm;
import org.bluedream.core.utils.UserUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @ClassName CustomLogoutFilter
* @Description TODO
* @Author Administrator
* @Data 2022/5/19 8:58
* @Version
*/
public class CustomLogoutFilter extends LogoutFilter {
private LoginOrgManager loginOrgManager;
public LoginOrgManager getLoginOrgManager() {
return loginOrgManager;
}
public void setLoginOrgManager(LoginOrgManager loginOrgManager) {
this.loginOrgManager = loginOrgManager;
}
/**
* 退出登录时,从 LoginOrgManager.resources 删除对应的组织信息
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = this.getSubject(request , response);
LoginOrgManager loginOrgManager = getLoginOrgManager();
LoginUserRealm userRealm = (LoginUserRealm) loginOrgManager.get((String) subject.getSession().getId());
if (UserUtil.isLoginOrg(userRealm , (String) subject.getSession().getId())){
loginOrgManager.remove((String) subject.getSession().getId());
}
return super.preHandle(request, response);
}
}
6.自定义realm:
package org.bluedream.core.config.shiro;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.bluedream.comm.utils.EmptyUtil;
import org.bluedream.core.config.exception.OrgNotPermitException;
import org.bluedream.core.config.exception.OrgNullPointException;
import org.bluedream.core.module.login.service.LoginService;
import org.bluedream.core.module.sys.entity.Menu;
import org.bluedream.core.module.sys.entity.Role;
import org.bluedream.core.module.sys.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
String loginCode = principals.getPrimaryPrincipal().toString();
return loginCode.equals("system")||super.isPermitted(principals, permission);
}
@Override
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
String loginCode = principal.getPrimaryPrincipal().toString();
return loginCode.equals("system")||super.hasRole(principal, roleIdentifier);
}
/**
* 授权信息
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
try {
// 获取登录用户
String loginCode = principalCollection.getPrimaryPrincipal().toString();
User user = loginService.getLoginUser(loginCode);
// 添加角色 、 权限 至shiro 权限管理器
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
List roleList = loginService.getRoleList(user);
List
7.shiro config配置类:
package org.bluedream.core.config.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.*;
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.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.bluedream.core.config.FilterChainDefinitionMap;
import org.bluedream.core.config.shiro.loginOrganization.LoginOrgManager;
import org.bluedream.core.utils.PasswordHelper;
import org.bluedream.core.utils.YmlRead;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.Map;
@Configuration
public class ShiroConfig {
// private String contextPath = (String) YmlRead.getValue("application.yml" , "server.servlet.context-path");
/**
* 自定义realm
* @return
*/
@Bean
public CustomRealm customRealm(){
CustomRealm realm = new CustomRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
securityManager.setSessionManager(sessionManager());
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 自定义 凭证匹配器
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME);
credentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS);
return credentialsMatcher;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 装载 securityManager! 必要配置
factoryBean.setSecurityManager(securityManager);
// login 认证控制器
factoryBean.setLoginUrl(YmlRead.getValueToString("application.yml", "shiro.loginUrl"));
// login success 跳转url
factoryBean.setSuccessUrl(YmlRead.getValueToString("application.yml", "shiro.successUrl"));
// login no role or no Permission todo: 验证失败的处理 待测试
factoryBean.setUnauthorizedUrl("/login");
Map filters = factoryBean.getFilters();
filters.put("authc" , this.authenticationFilter());
filters.put("logout" , this.logoutFilter());
FilterChainDefinitionMap chains = new FilterChainDefinitionMap();
chains.setDefaultFilterChainDefinitions(YmlRead.getValueToString("application.yml", "shiro.defaultFilterChainDefinitions"));
factoryBean.setFilterChainDefinitionMap(chains.getObject());
return factoryBean;
}
private LogoutFilter logoutFilter(){
CustomLogoutFilter logoutFilter = new CustomLogoutFilter();
logoutFilter.setLoginOrgManager(loginOrgManager());
logoutFilter.setRedirectUrl(YmlRead.getValueToString("application.yml", "shiro.loginUrl"));
return logoutFilter;
}
private FormAuthenticationFilter authenticationFilter(){
CustomFormAuthenticationFilter filter = new CustomFormAuthenticationFilter();
filter.setUsernameParam("loginCode");
filter.setOrgCodeParam("orgCode");
filter.setRememberMeParam("rememberMe");
filter.setLoginOrgManager(loginOrgManager());
return filter;
}
/**
* LifecycleBeanPostProcessor
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
* @return
*/
@Bean(name = {"lifecycleBeanPostProcessor"})
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 开启shiro注解 必要bean,依赖于lifecycleBeanPostProcessor
* 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
* 要在LifecycleBeanPostProcessor创建后才可以创建
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
/**
* AuthorizationAttributeSourceAdvisor
* 作用:加入shiro注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
/**
* session 会话管理设置
* @return
*/
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// session 失效时间
sessionManager.setGlobalSessionTimeout(1000*60*30);
// 删除无效session
sessionManager.setDeleteInvalidSessions(true);
/* // 去掉URL中的JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(true);
sessionManager.setSessionIdCookie(rememberMeCookie());*/
return sessionManager;
}
/**
* 记住我 设置
* @return
*/
@Bean
public RememberMeManager rememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
// 定义 rememberMe Cookie
rememberMeManager.setCookie(rememberMeCookie());
return rememberMeManager;
}
/**
* 记住我 cookie设置,需要注意,开启记住我,需要user对象能够实现序列化
* @return
*/
@Bean
public SimpleCookie rememberMeCookie(){
SimpleCookie cookie = new SimpleCookie();
// 设置cookie保存时长
cookie.setMaxAge(30*24*60*60);
// cookie中的名字
cookie.setName("rememberMe");
return cookie;
}
/**
* 前端 shiro标签
* @return
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
/**
* 注册 shiro 监听
* @return
*/
@Bean(name = "sessionListener")
public SessionListener sessionListener(){
return new CustomShiroSessionListener();
}
/**
* Session ID 生成器
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator(){
return new JavaUuidSessionIdGenerator();
}
/**
* todo 未完成
* @return
*/
public SessionDAO sessionDAO(){
MemorySessionDAO sessionDAO = new MemorySessionDAO();
return sessionDAO;
}
@Bean
public LoginOrgManager loginOrgManager(){
return new LoginOrgManager();
}
}
User工具类:
package org.bluedream.core.utils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.bluedream.comm.utils.EmptyUtil;
import org.bluedream.core.config.shiro.loginOrganization.LoginOrgManager;
import org.bluedream.core.config.shiro.loginOrganization.LoginUserRealm;
import org.bluedream.core.module.login.service.LoginService;
import org.bluedream.core.module.sys.entity.Organization;
import org.bluedream.core.module.sys.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
public class UserUtil {
private static Logger logger = LoggerFactory.getLogger(UserUtil.class);
private UserUtil(){
throw new AssertionError();
}
public static String getLoginOrgCode(){
LoginOrgManager manager = SpringUtil.getBean(LoginOrgManager.class);
String key = (String) getSession().getId();
LoginUserRealm realm = (LoginUserRealm) manager.get(key);
if (isLoginOrg(realm , key)){
return realm.getOrgCode();
}
return null;
}
/**
* 验证 LoginUserRealm 是否正确
* @param realm
* @param sessionIdValidity
* @return
*/
public static boolean isLoginOrg(LoginUserRealm realm , String sessionIdValidity){
/*
1. 如果 LoginUserRealm 为空,则返回 false
2. 验证 LoginOrgManager resources的key是否和session中的 LoginOrgManager.LOGIN_ORG_SUBJECT_SESSION_ID 是否一致
3. 验证 LoginUserRealm 中的 orgCode是否和session中的 LoginOrgManager.LOGIN_ORG_MANAGER_KEY 是否一致
*/
if (EmptyUtil.isEmpty(realm)){
return false;
}
HttpSession session = realm.getLoginOrgSession();
if (sessionIdValidity.equals(session.getAttribute(LoginOrgManager.LOGIN_ORG_SUBJECT_SESSION_ID))
&& realm.getOrgCode().equals(session.getAttribute(LoginOrgManager.LOGIN_ORG_MANAGER_KEY))){
return true;
}
return false;
}
public static Session getSession(){
try {
Subject subject = getSubject();
Session session = subject.getSession(false);
if (session == null){
return new SimpleSession();
}
return session;
}catch (UnavailableSecurityManagerException var2){
}catch (InvalidSessionException var3){
}
return new SimpleSession();
}
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
public static void testThreadContext(){
LoginOrgManager manager = SpringUtil.getBean(LoginOrgManager.class);
for (Map.Entry m1:manager.getResources().entrySet()
) {
System.out.println("login org key = " + m1.getKey() + "; login org value = " + m1.getValue());
}
};
}
Spring工具类:
package org.bluedream.core.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
public static T getBean(Class clazz){
return getApplicationContext().getBean(clazz);
}
public static T getBean(String name , Class clazz){
return getApplicationContext().getBean(name , clazz);
}
}
参考文档:Shiro基于组织机构的登录验证_sas???的博客-CSDN博客