前言:
我们知道shiro有一套自身的session管理机制,默认的session是存储在运行jvm内存中的,在单应用服务器中可共享session,但系统若为分布式架构,则不同应用服务器之间无法共享session,要实现不同应用服务器之间共享session,则需要重写SessionManager中的SessionDao,把session存储在缓存中,这里我们采用redis来存储,引用shiro-redis插件已为我们实现了SessionDao的重写,闲话少说上代码:
1.pom.xml配置依赖包
<!--shiro依赖包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<!--thymeleaf-shiro-extras-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
2.创建系统账户实体类
import java.io.Serializable;
/**
* 系统帐户
*/
public class Account implements Serializable{
private static final long serialVersionUID = 1L;
private String pk;//主键
private String xm;//姓名
private String username;//用户名
private String password;//密码
private String salt;//加密盐
public String getPk() {
return pk;
}
public void setPk(String pk) {
this.pk = pk;
}
public String getXm() {
return xm;
}
public void setXm(String xm) {
this.xm = xm;
}
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 String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
3.创建验证帐号密码实体类
import org.apache.shiro.authc.UsernamePasswordToken;
import cn.com.app.bean.Account;
/**
* 扩展具有Account对象 Token
*/
public class AccountLoginToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private Account account;
public AccountLoginToken(String username, char[] password, boolean rememberMe, String host, Account account) {
super(username, password, rememberMe, host);
this.account = account;
}
public AccountLoginToken(String username, String password, Account account) {
super(username, password);
this.account = account;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
4.创建ShiroRealm实现用户认证
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.AuthorizationException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import cn.com.app.bean.Account;
import cn.com.app.service.UserInfoService;
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserInfoService userInfoService;
/*
* Authorization 授权(权限操作验证,只有请求设置特定权限时调用此方法)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet<String>();
Set<String> permissions = new HashSet<String>();
Account userInfo = (Account) principals.fromRealm(getName()).iterator().next();
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("userid", userInfo.getPk());
try {
//根据用户id从数据库中查询用户角色
List<Map<String, Object>>userRoles = userInfoService.queryRoleByUserid(paramMap);
for (Map<String, Object> mapRole : userRoles) {
roles.add((String) mapRole.get("code"));
Map<String, String> parMap = new HashMap<String, String>();
parMap.put("roleid", (String) mapRole.get("roleid"));
//根据用户id从数据库中查询用户权限
List<Map<String, Object>>userPermissions = userInfoService.queryPermissionByRoleid(parMap);
for (Map<String, Object> mapPermission : userPermissions) {
permissions.add((String) mapPermission.get("code"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
//添加角色
info.addRoles(roles);
//添加权限
info.addStringPermissions(permissions);
return info;
}
/*
* Authentication 验证(登录认证)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
AccountLoginToken userInfoToken = (AccountLoginToken) authcToken;
String username = userInfoToken.getUsername();
SimpleAuthenticationInfo info = null;
try {
Map<String, Object> m = new HashMap<String, Object>();
m.put("username", username);
Map<String, Object> p = (Map<String, Object>) userInfoService.queryInfoByUsername(m);
Account userinfo = new Account();
userinfo.setPk(p.get("pk")==null?"":p.get("pk").toString());
userinfo.setXm(p.get("xm")==null?"":p.get("xm").toString());
userinfo.setUsername(p.get("username")==null?"":p.get("username").toString());
userinfo.setPassword(p.get("password")==null?"":p.get("password").toString());
userinfo.setSalt(p.get("salt")==null?"":p.get("salt").toString());
String password = userinfo.getPassword();
info = new SimpleAuthenticationInfo(userinfo, password.toCharArray(), getName());
if(userinfo.getSalt() != null && !"".equals(userinfo.getSalt())){
info.setCredentialsSalt(ByteSource.Util.bytes(userinfo.getSalt()));
}
} catch (Exception e) {
throw new AuthenticationException(e.getMessage(), e);
}
return info;
}
}
5.application.properties配置redis连接参数
# Redis 配置
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=100
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=5000
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000
6.创建shiro配置类
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
@Configuration
public class ShiroConfiguration {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
//将自己的验证方式加入容器
@Bean
public MyShiroRealm myShiroRealm(CredentialsMatcher credentialsMatcher) {
MyShiroRealm myShiroRealm = new MyShiroRealm();
//将自定义的令牌set到了Realm
myShiroRealm.setCredentialsMatcher(credentialsMatcher);
return myShiroRealm;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
//filterChainDefinitionMap.put("/logout", "logout");
//过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/login", "anon"); //不拦截登录请求
filterChainDefinitionMap.put("/script/**", "anon"); //不拦截js
filterChainDefinitionMap.put("/css/**", "anon"); //不拦截css
filterChainDefinitionMap.put("/js/**", "anon"); //不拦截js
filterChainDefinitionMap.put("/img/**", "anon"); //不拦截图片
filterChainDefinitionMap.put("/img2/**", "anon"); //不拦截图片
filterChainDefinitionMap.put("/public/**", "anon"); //不拦截公共资源
//filterChainDefinitionMap.put("/index", "roles[ADMIN]"); //设置特定角色访问
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/index");
// 登录成功后要跳转的链接
//shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager.setRealm(myShiroRealm());
securityManager.setRealm(myShiroRealm(hashedCredentialsMatcher()));
securityManager.setSessionManager(sessionManager());
//配置自定义缓存redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//session管理
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new MySessionListener());
sessionManager.setSessionListeners(listeners);
//设置session超时时间为1小时(单位毫秒)
//sessionManager.setGlobalSessionTimeout(3600000);
sessionManager.setGlobalSessionTimeout(-1);//永不超时
//设置redisSessionDao
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
//配置cacheManager
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
//配置redisManager
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
redisManager.setExpire(3600);//配置缓存过期时间(秒)
return redisManager;
}
//配置redisSessionDAO
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
//配置html页面支持shiro权限标签控制
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
//密码匹配凭证管理器
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//采用SHA-512方式加密
hashedCredentialsMatcher.setHashAlgorithmName("SHA-512");
//设置加密次数
hashedCredentialsMatcher.setHashIterations(1024);
//true加密用的hex编码,false用的base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
return hashedCredentialsMatcher;
}
}
7.在子系统1中登录用户
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(Account account,RedirectAttributes redirectAttributes, HttpSession httpSession,HttpServletRequest httpRequest) {
String forword = "redirect:/login";
AccountLoginToken token = new AccountLoginToken(account.getUsername(), account.getPassword(), account);
try {
SecurityUtils.getSubject().login(token);
Subject subject = SecurityUtils.getSubject();
Account currentUser = (Account) subject.getPrincipal();
forword = "redirect:/index";
subject.getSession().setAttribute("jh", currentUser.getJh());
subject.getSession().setAttribute("xm", currentUser.getXm());
subject.getSession().setAttribute("yhbh", currentUser.getPk());
subject.getSession().setAttribute("sessionid", subject.getSession().getId());
System.out.println("登录成功");
}catch (UnknownAccountException uae) {
redirectAttributes.addFlashAttribute("errorMsg", "用户不存在!");
} catch (IncorrectCredentialsException ice) {
redirectAttributes.addFlashAttribute("errorMsg", "密码不正确!");
} catch (LockedAccountException lae) {
redirectAttributes.addFlashAttribute("errorMsg", "账户被锁定!");
} catch (ExcessiveAttemptsException eae) {
redirectAttributes.addFlashAttribute("errorMsg", "密码尝试限制!");
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("errorMsg", "该帐号未注册!");
}
return forword;
}
8.子系统2中获取用户信息
@RequestMapping("/getAccount")
@ResponseBody
public String getAccount() {
Subject subject = SecurityUtils.getSubject();
Account currentUser = (Account) subject.getPrincipal();
System.out.println("登录用户姓名:"+currentUser.getXm());
return currentUser.getXm();
}
9.退出登录,子系统2获取信息失败,需子系统1重新登录后才能获取
@RequestMapping("/logout")
@ResponseBody
public String logout(){
SecurityUtils.getSubject().logout();
return "退出登录";
}
MySessionListener监听类:
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.springframework.stereotype.Component;
@Component
public class MySessionListener implements SessionListener {
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.incrementAndGet();
//System.out.println("登录+1=="+sessionCount.get());
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
//System.out.println("登录退出-1=="+sessionCount.get());
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
//System.out.println("登录过期-1=="+sessionCount.get());
}
public int getSessionCount() {
return sessionCount.get();
}
}
备注:需提前开启Redis服务,分布式系统中的每个子系统都需要配置Shiro框架