动吧旅游生态系统——用户管理模块——shiro权限认证分析
访问网页需要验证登录、不同的用户访问同一资源有不同的权限等都可以通过shiro实现认证。
1.shiro介绍
2.业务分析
2.1 访问资源流程
anon、authc等是一些默认的过滤器,而这些过滤器由shiroFilterFactory工厂提供,而该工厂可以由shiroFilterFactoryBean对象创建,所以在配置类中需要配置该bean对象。
过滤器在servlet之前执行,而springmvc集成了servlet,即在controller前对资源进行了拦截,拦截后会调用securityManager进行认证,该对象定义了一系列的认证授权方法,一旦遇到需要认证的请求就跳回登录页面
shiroFilterFactoryBean对象规定了哪些资源需要认证访问,哪些可以直接访问,而具体有没有认证需要交给securityManager去检测该请求有没有被认证
2.2 登录验证
登陆界面输入的用户信息controller可以拿到,通过subject传给securutyManager,Realm中可以获取用户信息
放行登录的contrller
调用doLogin后 security Manager会将token交给认证管理器,认证管理器再交给realm做具体认证
2.3 授权分析
Shiro授权基于spring原生aop实现
Security Manager交给权限管理器去进行授权
2.4使用缓存
当我们进行授权操作时,每次都会从数据库查询用户权限信息,为了提高授权性能,可以将用户权限信息查询出来以后进行缓存,下次授权时从缓存取数据即可。
CacheManager是管理cache的一个对象,底层是用的softHashmap()对象,软引用,表示内存不足,如果GC触发,容器中关联的对象可以被回收。
2.5shiro记住我
使用session Manager和cookie Manager
过滤器改成user,表明可以从浏览器的cookie获取用户信息,
设置cookie的超时时间,不设置默认是会话cookie,浏览器关闭即结束,再打开浏览器cookie无效。
添加url重写,避免客户端将cookie禁用,无法通过cookie记录该客户端信息,通过重写的url后缀可以识别是同一客户端。
使用shiro框架实现认证操作,用户登录成功会将用户信息写入到会话对象中,其默认时长为30分钟,session的超时时间从对该页面不做任何操作开始计算,到达时间相当于锁屏,会清空session,也就表明需要重新登录
3.代码呈现
UserController中调用登录方法,subject主体携带用户信息的token,交给security Manager,security Manager交给认证管理器,认证管理器再交给realm去认证,ream中token包含了用户信息
@RequestMapping("doLogin")
public JsonResult doLogin(String username,String password,boolean isRememberMe){
//1.获取Subject对象
Subject subject = SecurityUtils.getSubject();
//2.通过Subject提交用户信息,交给shiro框架进行认证操作
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(isRememberMe);
subject.login(token);
//1)token会传给shiro的SecurityManager
//2)SecurityManager将token传递给认证管理器
//3)认证管理器会将token传递给realm
return new JsonResult("login ok");
}
配置类 分析可知:需要security Manager、工厂对象
package com.cy.pj.common.config;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration//配置类——可以直接写在启动类中,交给spring管理的一个配置对象
public class SpringShiroConfig {
@Bean//此注解描述的方法的返回值交给spring管理
public SecurityManager securityManager(Realm realm,CacheManager cacheManager,
RememberMeManager rememberMeManager,
SessionManager sessionManager){
DefaultWebSecurityManager sManager= new DefaultWebSecurityManager();
sManager.setRealm(realm);
sManager.setCacheManager(cacheManager);
sManager.setRememberMeManager(rememberMeManager);
sManager.setSessionManager(sessionManager);
return sManager;
}
@Bean//设置一些访问规则——匿名访问的资源,认证的访问资源
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean sFBean = new ShiroFilterFactoryBean();
sFBean.setSecurityManager(securityManager);//这个请求是否已认证交给securityManager去做
sFBean.setLoginUrl("/doLoginUI");//放行登录页面
sFBean.setSuccessUrl("/doIndexUI");
//定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
LinkedHashMap<String,String> map= new LinkedHashMap<>();
//静态资源允许匿名访问:"anon"
map.put("/bower_components/**","anon");
map.put("/build/**","anon");
map.put("/dist/**","anon");
map.put("/plugins/**","anon");
map.put("/user/doLogin","anon");
map.put("/doLogout","logout");
//除了匿名访问的资源,其它都要认证("authc")后访问
// map.put("/**","authc");
map.put("/**","user");//记住我
sFBean.setFilterChainDefinitionMap(map);
return sFBean;
}
@Bean
public CacheManager shiroCacheManager(){
//具体使用的是softHashmap,软引用cache,好处在于内存不够时淘汰缓存中的数据
return new MemoryConstrainedCacheManager();
}
@Bean
public RememberMeManager cookieRememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(7*24*60*60);//7天
//不设置时间相当于临时cookie,浏览器关闭cookie生命周期结束,设置生命周期,关闭浏览器cookie信息还存在
cookieRememberMeManager.setCookie(cookie);
return cookieRememberMeManager;
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sManager= new DefaultWebSessionManager();
sManager.setGlobalSessionTimeout(60*60*1000);//60分钟
sManager.setSessionIdUrlRewritingEnabled(false);//不启用url重写,
return sManager;
}
}
realm相当于service实现类,去做具体的认证和授权
package com.cy.pj.sys.service.realm;
import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysUser;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class ShiroUserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
@Autowired
private SysMenuDao sysMenuDao;
@Autowired
private SysUserRoleDao sysUserRoleDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Override//重写get或set方法都可以——凭证匹配器,返回密码匹配对象——解密方式
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
//构建凭证匹配对象
HashedCredentialsMatcher cMatcher= new HashedCredentialsMatcher();
//设置加密算法
cMatcher.setHashAlgorithmName("MD5");
//设置加密次数
cMatcher.setHashIterations(1);
super.setCredentialsMatcher(cMatcher);
}
/* @Override
public CredentialsMatcher getCredentialsMatcher(){
HashedCredentialsMatcher cMatcher = new HashedCredentialsMatcher();
cMatcher.setHashAlgorithmName("MD5");
cMatcher.setHashIterations(1);
return cMatcher;
}*/
@Override
//权限认证——查询角色——查询对应的菜单 ——用aop,在方法上自定义注解,根据方法上的注解和用户拥有的权限是否一致
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user=(SysUser)principals.getPrimaryPrincipal();//获取用户身份,和登录时封装的对应
Integer userId = user.getId();
//基于用户Id查询对应角色
List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(userId);
if(roleIds==null||roleIds.size()==0)
throw new AuthorizationException();
//基于角色Ids查询对应菜单
List<Integer> menuIds = sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);
if (menuIds==null||menuIds.size()==0)
throw new AuthorizationException();
//基于菜单Ids获取permission
List<String> permissions = sysMenuDao.findPermissions(menuIds);
//对权限信息进行封装——封装为set是因为下面的方法setStringPermissions()中的形参需要set类型的参数
Set<String> set=new HashSet<>();
for(String per:permissions){
if(!StringUtils.isEmpty(per)){
set.add(per);
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(set);
return info;
}
@Override
//登录验证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户名(用户页面输入)
UsernamePasswordToken upToken=(UsernamePasswordToken)token;
String username = upToken.getUsername();
//2.基于用户名查询用户信息
SysUser user = sysUserDao.findUserByUserName(username);
//3.判定用户是否存在
if(user==null)throw new UnknownAccountException();
//4.判定用户是否已被禁用
if(user.getValid()==0)throw new LockedAccountException();
///5.封装用户信息
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());//将盐值转换成credentialsSalt
//principal凭证/身份,hashedCredentials加密的密码,credentialsSalt凭证盐,realmName
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, this.getName());
//6.返回封装结果
return info;//返回值会传递给认证管理器,认证管理器会通过此信息完成认证操作
}
}
本文地址:https://blog.csdn.net/cassiel_yuan/article/details/109635372
上一篇: 数据结构与算法---数组、排序---合并两个有序数组、 颜色分类
下一篇: 多线程和JUC