用戶進行過認證登陸後,某些接口是有權限限制的;如何實現只有相應權限的用戶才能夠調用相應接口css
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 源碼java
// // 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; } } }
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 對象中web
一個用戶已經認證登陸成功,根據他所擁有的權限來斷定他能夠訪問那些接口spring
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 接口sql
原理請參見 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; } }
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"; // 認證登陸失敗就返回提示信息,在前端進行登陸頁面跳轉 // } // } }
從 AuthorizingRealm 源碼能夠看出, AuthorizingRealm 能夠依賴注入 CacheManager 來實現緩存apache
// // 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; } }