Shrio00 Shiro角色授权、Shiro权限授权、开启Shiro缓存
1 需求01
用户进行过认证登录后,某些接口是有权限限制的;如何实现只有相应权限的用户才可以调用相应接口
2 修改shiro配置类 ShiroConfiguration
package cn.xiangxu.apache_shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; /** * Shiro配置类 */ @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 定义登录的url shiroFilterFactoryBean.setSuccessUrl("/index"); // 定义登录成功后的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 没有权限时跳转的url LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 请求拦截配置 filterChainDefinitionMap.put("/index", "authc"); filterChainDefinitionMap.put("/login", "anon"); // 排除 login 的验证 filterChainDefinitionMap.put("/loginUser", "anon"); // 排除 loginUser 的验证 filterChainDefinitionMap.put("/admin", "roles[admin]"); filterChainDefinitionMap.put("/**", "user"); // 所有请求都必须进行登录过滤 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(authRealm); return defaultWebSecurityManager; } /** 注入自定义密码比较器 */ @Bean public CredentialMather credentialMather() { return new CredentialMather(); } /** 注入自定义的授权、认证登录类 */ @Bean public AuthRealm authRealm(@Qualifier("credentialMather") CredentialMather credentialMather) { AuthRealm authRealm = new AuthRealm(); authRealm.setCredentialsMatcher(credentialMather); return authRealm; } /** Shiro与Spring整合配置 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } }
代码解释:只用用户角色为 admin 的用户才可以访问 /admin 接口
原理请参见 RolesAuthorizationFilter 源码
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.shiro.web.filter.authz; import java.io.IOException; import java.util.Set; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.CollectionUtils; public class RolesAuthorizationFilter extends AuthorizationFilter { public RolesAuthorizationFilter() { } public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = this.getSubject(request, response); String[] rolesArray = (String[])((String[])mappedValue); if (rolesArray != null && rolesArray.length != 0) { Set<String> roles = CollectionUtils.asSet(rolesArray); return subject.hasAllRoles(roles); } else { return true; } } }
3 修改授权、认证登录配置类 AuthRealm
package cn.xiangxu.apache_shiro; import cn.xiangxu.apache_shiro.model.Permission; import cn.xiangxu.apache_shiro.model.Role; import cn.xiangxu.apache_shiro.model.User; import cn.xiangxu.apache_shiro.service.UserService; import org.apache.commons.collections.CollectionUtils; 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.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * 自定义授权、认证登录类 */ public class AuthRealm extends AuthorizingRealm { @Autowired private UserService userService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { User user = (User)principalCollection.fromRealm(this.getClass().getName()).iterator().next(); // 从session中获取用户信息 List<String> permissionList = new ArrayList<>(); // 用于存放用户的权限列表 List<String> roleNameList = new ArrayList<>(); // 用于存放用户角色名称 Set<Role> roleSet = user.getRoleSet(); // 从用户信息中获取用户角色 if (CollectionUtils.isNotEmpty(roleSet)) { for (Role role : roleSet) { roleNameList.add(role.getRname()); Set<Permission> permissionSet = role.getPermissionSet(); if (CollectionUtils.isNotEmpty(permissionSet)) { for (Permission permission : permissionSet) { permissionList.add(permission.getPname()); } } } } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(permissionList); info.addRoles(roleNameList); return info; } // 认证登录(使用用户名和密码进行登录认证) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)authenticationToken; String username = usernamePasswordToken.getUsername(); // 取出用户名 User user = userService.findByUsername(username); // 根据用户名到数据库中去获取用户信息 return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName()); } }
代码解释:将用户的角色名称提取出来并添加到 SimpleAuthorizationInfo 对象中
4 需求02
一个用户已经认证登录成功,根据他所拥有的权限来判定他可以访问那些接口
5 重写shiro配置类 ShiroConfiguration
package cn.xiangxu.apache_shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; /** * Shiro配置类 */ @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 定义登录的url shiroFilterFactoryBean.setSuccessUrl("/index"); // 定义登录成功后的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 没有权限时跳转的url LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 请求拦截配置 filterChainDefinitionMap.put("/index", "authc"); filterChainDefinitionMap.put("/login", "anon"); // 排除 login 的验证 filterChainDefinitionMap.put("/loginUser", "anon"); // 排除 loginUser 的验证 filterChainDefinitionMap.put("/admin", "roles[admin]"); // 角色限定 filterChainDefinitionMap.put("/edit", "perms[edit]"); // 权限限定 filterChainDefinitionMap.put("/**", "user"); // 所有请求都必须进行登录过滤 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(authRealm); return defaultWebSecurityManager; } /** 注入自定义密码比较器 */ @Bean public CredentialMather credentialMather() { return new CredentialMather(); } /** 注入自定义的授权、认证登录类 */ @Bean public AuthRealm authRealm(@Qualifier("credentialMather") CredentialMather credentialMather) { AuthRealm authRealm = new AuthRealm(); authRealm.setCredentialsMatcher(credentialMather); return authRealm; } /** Shiro与Spring整合配置 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } }
代码解释:只有拥有 edit 权限的用户才可以访问 /edit 接口
原理请参见 PermissionsAuthorizationFilter 源码
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.shiro.web.filter.authz; import java.io.IOException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.subject.Subject; public class PermissionsAuthorizationFilter extends AuthorizationFilter { public PermissionsAuthorizationFilter() { } public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = this.getSubject(request, response); String[] perms = (String[])((String[])mappedValue); boolean isPermitted = true; if (perms != null && perms.length > 0) { if (perms.length == 1) { if (!subject.isPermitted(perms[0])) { isPermitted = false; } } else if (!subject.isPermittedAll(perms)) { isPermitted = false; } } return isPermitted; } }
6 接口总汇
package cn.xiangxu.apache_shiro.controller; import cn.xiangxu.apache_shiro.model.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; //@RestController // 前后端分离时使用的注解 @Controller // 前后端不分离使用的注解 public class TestController { @RequestMapping(value = "/unauthorized") public String unauthorized() { return "unauthorized"; } @GetMapping("/login") public String login() { return "login"; } @GetMapping(value = "/index") public String index() { return "index"; } @GetMapping(value = "/logout") public String logout() { Subject subject = SecurityUtils.getSubject(); if (subject != null) { subject.logout(); } return "login"; } @GetMapping(value = "/admin") @ResponseBody public String admin() { return "admin success"; } @GetMapping(value = "/edit") @ResponseBody public String edit() { return "edit success"; } // 前后端不分离的写法 @PostMapping("/loginUser") public String loginUser( @RequestParam("username") String username, @RequestParam("password") String password, HttpSession session ) { System.out.println("进入登录接口"); UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); // 获取主体 // 认证登录逻辑(调用AuthRealm类的相关方法进行认证登录) try { subject.login(token); // 调用主体的login方法进行认证登录 User user = (User)subject.getPrincipal(); // 如果认证登录成功后就可以从主体中获取到数据库中主体对应的信息 session.setAttribute("user", user); // 将从数据库中获取到的主体信息放到session中,以便在权限验证的时候使用 return "index"; // 认证登录成功后就进入主页面 } catch (Exception e) { e.printStackTrace(); return "login"; // 认证登录失败就进入登录页面 } } // 前后端分离的写法 // @PostMapping("/loginUser") // public String loginUser( // @RequestBody User formUser, // HttpSession session // ) { // System.out.println("进入登录接口" + formUser); // UsernamePasswordToken token = new UsernamePasswordToken(formUser.getUsername(), formUser.getPassword()); // Subject subject = SecurityUtils.getSubject(); // // try { // System.out.println("进入捕获01"); // subject.login(token); // User user = (User)subject.getPrincipal(); // System.out.println(user); // session.setAttribute("user", user); // System.out.println("进入捕获02"); // return "index"; // 热证登录成功就返回提示信息,在前端进行主页面跳转 // } catch (Exception e) { // e.printStackTrace(); // return "login"; // 认证登录失败就返回提示信息,在前端进行登录页面跳转 // } // } }
7 开启Shiro缓存
从 AuthorizingRealm 源码可以看出, AuthorizingRealm 可以依赖注入 CacheManager 来实现缓存
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.shiro.cache; public interface CacheManager { <K, V> Cache<K, V> getCache(String var1) throws CacheException; }
技巧:CacheManager有两个实现类 -> 一个是MemoryConstrainedCacheManager, 一个是AbstractCacheManager
只需要在shiro配置类中注入authRealm这个bean时添加一行代码就可以开启shiro缓存功能
package cn.xiangxu.apache_shiro; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; /** * Shiro配置类 */ @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 定义登录的url shiroFilterFactoryBean.setSuccessUrl("/index"); // 定义登录成功后的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 没有权限时跳转的url LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 请求拦截配置 filterChainDefinitionMap.put("/index", "authc"); filterChainDefinitionMap.put("/login", "anon"); // 排除 login 的验证 filterChainDefinitionMap.put("/loginUser", "anon"); // 排除 loginUser 的验证 filterChainDefinitionMap.put("/admin", "roles[admin]"); // 角色限定 filterChainDefinitionMap.put("/edit", "perms[edit]"); // 权限限定 filterChainDefinitionMap.put("/**", "user"); // 所有请求都必须进行登录过滤 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(authRealm); return defaultWebSecurityManager; } /** 注入自定义密码比较器 */ @Bean public CredentialMather credentialMather() { return new CredentialMather(); } /** 注入自定义的授权、认证登录类 */ @Bean public AuthRealm authRealm(@Qualifier("credentialMather") CredentialMather credentialMather) { AuthRealm authRealm = new AuthRealm(); authRealm.setCacheManager(new MemoryConstrainedCacheManager()); // 开启内存缓存 authRealm.setCredentialsMatcher(credentialMather); return authRealm; } /** Shiro与Spring整合配置 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } }
package com.mmall.demo2; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; import javax.sql.DataSource; @Configuration public class DruidConfiguration { @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); //白名单: servletRegistrationBean.addInitParameter("allow", "127.0.0.1"); //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的即提示:Sorry, you are not permitted to view this page. servletRegistrationBean.addInitParameter("deny", "192.168.1.100"); //登录查看信息的账号密码. servletRegistrationBean.addInitParameter("loginUsername", "druid"); servletRegistrationBean.addInitParameter("loginPassword", "12345678"); //是否能够重置数据. servletRegistrationBean.addInitParameter("resetEnable", "false"); return servletRegistrationBean; } @Bean public FilterRegistrationBean statFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter()); //添加过滤规则. filterRegistrationBean.addUrlPatterns("/*"); //添加不需要忽略的格式信息. filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } @Bean PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() { return new PersistenceExceptionTranslationPostProcessor(); } //配置数据库的基本链接信息 @Bean(name = "dataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource") //可以在application.properties中直接导入 public DataSource dataSource() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); } @Bean public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); bean.setMapperLocations(resolver.getResources("classpath:/mappers/*.xml")); return bean; } }