Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Apache Shiro 体系结构
1、Authentication 认证 ---- 用户登录
2、Authorization 授权 — 用户具有哪些权限
3、Cryptography 安全数据加密
4、Session Management 会话管理
5、Web Integration web系统集成
6、Interations 集成其它应用,spring、缓存框架
详情参考官方网站http://shiro.apache.org/
二 .Spring Boot整合Shiro**
<!-- shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
1.自定义 Realm
有了数据库表和数据,我们开始自定义 Realm。自定义 Realm 需要继承 AuthorizingRealm 类,该类封装了很多方法,且继承自 Realm 类。
继承 AuthorizingRealm 类后,我们需要重写以下两个方法。
doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。
具体实现如下,相关注解请见代码注释:
/**
*
* 身份认证realm; 1. 认证 2. 授权
*
* @author zm
* @date 2020年9月9号
*/
public class MyShiroRealm extends AuthorizingRealm {
protected final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Autowired
private MenuService menuService;
public MyShiroRealm() {
super();
}
public MyShiroRealm(CacheManager cacheManager) {
super(cacheManager);
}
/******************************************************************************认证******************************************************************************/
/**
* 认证回调函数, 登录时调用
* 没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取基于用户名和密码的令牌
SysAuthToken authcToken = (SysAuthToken) token;
// logger.info("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(authcToken, ToStringStyle.MULTI_LINE_STYLE));
// 此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
// return fackAuthenticationInfo(authcToken);
return jdbcAuthenticationInfo(authcToken);
}
/*
private AuthenticationInfo fackAuthenticationInfo(SysAuthToken token) {
AuthenticationInfo authcInfo = null;
String kapatchaExist = (String) ShiroUtils.getSessionAttribute(AppConstants.Session.CAPTCHA);
if(!kapatchaExist.equals(token.getCaptcha())){
if (AppConstants.Global.ADMIN_USER_ID.equals(token.getUsername())) {//超级管理员
String passwordMd5 = MD5Util.getMD5(AppConstants.Global.ADMIN_PASSWORD);
authcInfo = new SimpleAuthenticationInfo(AppConstants.Global.ADMIN_USER_ID, passwordMd5, this.getName());
User userAdmin = new User();
userAdmin.setUserId(AppConstants.Global.ADMIN_USER_ID);
userAdmin.setUserName(AppConstants.Global.ADMIN_USER_NAME);
userAdmin.setPassword(passwordMd5);
ShiroUtils.setSessionAttribute(AppConstants.Session.CURRENT_USER, userAdmin);
}
}
return authcInfo;
}*/
/**
* 查询数据库验证认证
*
* @param token
* @return
*/
private AuthenticationInfo jdbcAuthenticationInfo(SysAuthToken token) {
AuthenticationInfo authcInfo = null;
/*
String kapatchaExist = (String) ShiroUtils.getSessionAttribute(AppConstants.Session.CAPTCHA);
if(!kapatchaExist.equals(token.getCaptcha())){
//校验验证码
}
*/
User userRst = new User();
if (AppConstants.Global.ADMIN_USER_ID.equals(token.getUsername())) {//超级管理员
String passwordMd5 = MD5Util.getMD5(AppConstants.Global.ADMIN_PASSWORD);
authcInfo = new SimpleAuthenticationInfo(AppConstants.Global.ADMIN_USER_ID, passwordMd5, this.getName());
userRst.setUserId(AppConstants.Global.ADMIN_USER_ID);
userRst.setUserName(AppConstants.Global.ADMIN_USER_NAME);
userRst.setPassword(passwordMd5);
Group groupAdmin = new Group();
groupAdmin.setGroupId("admin_group");
groupAdmin.setGroupName("管理员组");
userRst.setGroup(groupAdmin);
}else{//其他用户
User userQ = new User();
userQ.setUserId(token.getUsername());
userRst = userService.get(userQ);
if(userRst.getActive() == 1){//用户激活,可以登录
authcInfo = new SimpleAuthenticationInfo(userRst.getUserId(), userRst.getPassword(), this.getName());
}
}
ShiroUtils.setSessionAttribute(AppConstants.Session.CURRENT_USER, userRst);
return authcInfo;
}
/******************************************************************************授权******************************************************************************/
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// return fackAuthorizationInfo(principals);
return jdbcAuthorizationInfo(principals);
}
/*
private AuthorizationInfo fackAuthorizationInfo(PrincipalCollection principals) {
String loginId = (String) super.getAvailablePrincipal(principals);
MyAuthorizationInfo myAuthorInfo = new MyAuthorizationInfo();
if (null != loginId && AppConstants.Global.ADMIN_USER_ID.equals(loginId)) {
// 添加一个角色
myAuthorInfo.addRole("appadmin");
// 添加权限
List<String> userPermissions = new ArrayList<String>();
userPermissions.add("aa:user:view");
userPermissions.add("aa:user:modify");
myAuthorInfo.addStringPermissions(userPermissions);
logger.info("已为用户[admin]赋予了[sysadmin]角色和[user*]相关权限");
return myAuthorInfo;
}
return null;
}*/
/**
* 查询DB或缓存, 获取授权信息
* @param principals
* @return
*/
private AuthorizationInfo jdbcAuthorizationInfo(PrincipalCollection principals) {
// 获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()
String loginId = (String) super.getAvailablePrincipal(principals);
List<String> roleIdList = new ArrayList<String>();
List<String> permissionIdList = new ArrayList<String>();
List<String> menuIdList = new ArrayList<String>();
List<Menu> menus = new ArrayList<Menu>();
//获取当前登录用户的详细信息
User user = (User) ShiroUtils.getSessionAttribute(AppConstants.Session.CURRENT_USER);
if (null != user) {
if(AppConstants.Global.ADMIN_USER_ID.equals(loginId)){//超级管理员拥有所有角色和权限
List<Role> roleList = roleService.findList(new Role());
if(null != roleList && roleList.size()>0){
for (Role role : roleList) {// 获取角色
roleIdList.add(role.getRoleId());
}
}
List<Permission> permissionList = permissionService.findList(new Permission());
if(null != permissionList && permissionList.size()>0){
for (Permission permission : permissionList) {// 获取权限
permissionIdList.add(permission.getPermissionId());
}
}
List<Menu> menuList = menuService.findList(new Menu());
if (null != menuList && menuList.size() > 0) {// 获取权限
for (Menu menu : menuList) {
menuIdList.add(menu.getMenuId());
menus.add(menu);
}
}
}else{//普通用户
if (null != user.getRoleList() && user.getRoleList().size() > 0) {
for (Role role : user.getRoleList()) {// 获取角色
roleIdList.add(role.getRoleId());
// 实体类Role中包含有角色权限的实体类信息
// 获取角色的权限
List<Permission> permissionList = permissionService.findRolePermission(role);
role.setPermissionList(permissionList);
if (null != permissionList && permissionList.size() > 0) {// 获取权限
for (Permission permission : role.getPermissionList()) {
permissionIdList.add(permission.getPermissionId());
}
}
//获取角色的菜单
List<Menu> menuList = menuService.findRoleMenu(role);
role.setMenuList(menuList);
if (null != menuList && menuList.size() > 0) {// 获取权限
for (Menu menu : role.getMenuList()) {
menuIdList.add(menu.getMenuId());
menus.add(menu);
}
}
}
// TODO 获取当前登录用户所属组的角色
}
}
} else {
throw new AuthorizationException();
}
// 为当前用户设置角色和权限
MyAuthorizationInfo myAuthorInfo = new MyAuthorizationInfo();
myAuthorInfo.addRoles(roleIdList);
myAuthorInfo.addStringPermissions(permissionIdList);
myAuthorInfo.addStringMenus(menuIdList);
user.setRoleIdList(roleIdList);
user.setPermissionIdList(permissionIdList);
user.setMenuIdList(menuIdList);
user.addMenus(menus);
ShiroUtils.setSessionAttribute(AppConstants.Session.CURRENT_USER, user);
return myAuthorInfo;
}
}
然后,将该用户的相关信息封装到 authcInfo 中并返回给 Shiro。接下来就该 Shiro 上场了,将封装的用户信息与用户的输入信息(用户名、密码)进行对比、校验(注意,这里对密码也要进行校验)。校验通过则允许用户登录,否则跳转到指定页面。
同理,权限验证时,也需先根据用户名从数据库中获取其对应的角色和权限,将其封装到 authorizationInfo 并返回给 Shiro。
Shiro 配置
/**
*
* @author zm
* @date 2020年9月9号
*/
@Configuration
public class ShiroConfig {
protected final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
private static final boolean redisSession = false;
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
* Filter Chain定义说明
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.ftl"页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/dashboard");// 登录成功后要跳转的链接
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403error");// 未授权界面;
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接,按顺序判断, 从上向下顺序执行,一般将 /**放在最为下边
// authc: 所有url都必须认证通过才可以访问;
// anon: 所有url都都可以匿名访问
filterChainDefinitionMap.put("/assets/dist/dist/**", "roles[jtksh]");
filterChainDefinitionMap.put("/assets/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/downloadExe", "anon");
filterChainDefinitionMap.put("/sys/404", "anon");
filterChainDefinitionMap.put("/sys/500", "anon");
//数据接口
filterChainDefinitionMap.put("/jtzx/linkJam/save", "anon");
filterChainDefinitionMap.put("/jtzx/linkJam/allData", "anon");
filterChainDefinitionMap.put("/dev/vms/callBackCurPlayItem", "anon");
filterChainDefinitionMap.put("/jcsj/parkingLot/updateParkingLots", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/captchaImg", "anon");
filterChainDefinitionMap.put("/ajaxLogin", "anon");
filterChainDefinitionMap.put("/test/**", "anon");
filterChainDefinitionMap.put("/error/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/autoGuardLine/**", "anon");
filterChainDefinitionMap.put("/emsg/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/dashboard", "authc");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources", "anon");
filterChainDefinitionMap.put("/api-docs", "anon");
filterChainDefinitionMap.put("/springfox-swagger-ui/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/ui/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/security", "anon");
/**
* 插入微博爬取数据接口,不拦截
*/
filterChainDefinitionMap.put("/insert**", "anon");
filterChainDefinitionMap.put("/**", "authc");
// start test by Yang Yong
filterChainDefinitionMap.put("/**", "anon"); //设置所有请求无需登录
filterChainDefinitionMap.put("/jw/getAllDeviceStatus", "anon");
// end test by Yang Yong
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义realm.
securityManager.setRealm(myShiroRealm());
securityManager.setSessionManager(sessionManager());
if(redisSession){
securityManager.setCacheManager(getRedisCacheManager());
}
return securityManager;
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
int serverTimeoutSec = Integer.valueOf(Global.getConfig("server.session.timeout"));
sessionManager.setGlobalSessionTimeout(serverTimeoutSec* 1000);
//定期清理所有失效的Session对象
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler(sessionManager));
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
/*
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new MySessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookieEnabled(true);
*/
if(redisSession){
sessionManager.setSessionIdCookie(getSessionIdCookie());
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionDAO(getRedisSessionDAO());
}else{
sessionManager.setSessionDAO(getMemorySessionDAO());
}
return sessionManager;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler(DefaultWebSessionManager sessionManager) {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler(sessionManager);
scheduler.setInterval(60*1000);//60秒清理一次
return scheduler;
}
@Bean(name = "sessionIdCookie")
public SimpleCookie getSessionIdCookie() {
SimpleCookie cookie = new SimpleCookie("sid");
cookie.setHttpOnly(true);
return cookie;
}
@Bean
@Primary
public SessionDAO getMemorySessionDAO() {
MemorySessionDAO memorySessionDAO = new MemorySessionDAO();
return memorySessionDAO;
}
@Bean
public RedisSessionDAO getRedisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
return redisSessionDAO;
}
@Bean
public MyShiroRealm myShiroRealm() {
//需要认证和授权双缓存
MyShiroRealm myShiroRealm = null;
if(redisSession){
myShiroRealm = new MyShiroRealm(getRedisCacheManager());
}else{
myShiroRealm = new MyShiroRealm();
}
myShiroRealm.setAuthenticationCachingEnabled(false);
myShiroRealm.setAuthorizationCachingEnabled(false);
return myShiroRealm;
}
@Bean
public MyRedisCacheManager getRedisCacheManager() {
return new MyRedisCacheManager();
}
/*
@Bean(name = "shiroEhcacheManager")
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache/ehcache.xml");
return em;
}
*/
/**
* 权限注解的支持
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return aasa;
}
}
配置 Shiro 过滤器时,我们引入了安全管理器。
至此,我们可以看出,Shiro 配置一环套一环,遵循从 Reaml 到 SecurityManager 再到 Filter 的过程。在过滤器中,我们需要定义一个 shiroFactoryBean,然后将 SecurityManager 引入其中,需要配置的内容主要有以下几项。
默认登录的 URL:身份认证失败会访问该 URL。
认证成功之后要跳转的 URL。
权限认证失败后要跳转的 URL。
需要拦截或者放行的 URL:这些都放在一个 Map 中。
通过上面的代码,我们也了解到, Map 中针对不同的 URL有不同的权限要求,下表总结了几个常用的权限
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
然后启动你的是springboot工程 请求一个路径他就会拦截跳转到login登录页面的
- [待续 下一篇文章 springboot整个springSecurity ]///