第一步、引入 pomjava
<!--springboot 整合 shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
第二步、定義 shiroConfigweb
import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // setLoginUrl 若是不設置值,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 或 "/login" 映射 shiroFilterFactoryBean.setLoginUrl("/login"); // 設置無權限時跳轉的 url; shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); // 設置攔截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //遊客,開發權限 filterChainDefinitionMap.put("/guest/**", "anon"); //用戶,須要角色權限 「user」 filterChainDefinitionMap.put("/user/**", "roles[user]"); //管理員,須要角色權限 「admin」 filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //開放登錄接口 filterChainDefinitionMap.put("/login", "anon"); //其他接口一概攔截 //主要這行代碼必須放在全部權限設置的最後,否則會致使全部 url 都被攔截 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println("Shiro攔截器工廠類注入成功"); return shiroFilterFactoryBean; } /** * 注入 securityManager */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 設置realm. securityManager.setRealm(customRealm()); return securityManager; } /** * 自定義身份認證 realm; * <p> * 必須寫這個類,並加上 @Bean 註解,目的是注入 CustomRealm, * 不然會影響 CustomRealm類 中其餘類的依賴注入 */ @Bean public CustomRealm customRealm() { return new CustomRealm(); } }
注意:spring
anon, authc, authcBasic, user 是第一組認證過濾器,perms, port, rest, roles, ssl 是第二組受權過濾器,要經過受權過濾器,就先要完成登錄認證操做(即先要完成認證才能前去尋找受權) 才能走第二組受權器(例如訪問須要 roles 權限的 url,若是尚未登錄的話,會直接跳轉到 shiroFilterFactoryBean.setLoginUrl(); 設置的 url )
數據庫
第三步、定義 realm apache
繼承 AuthorizingRealm 類,重寫 doGetAuthenticationInfo 和 doGetAuthorizationInfo 兩個方法安全
import org.apache.shiro.SecurityUtils; 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.HashSet; import java.util.Set; public class CustomRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 獲取身份驗證信息 * Shiro中,最終是經過 Realm 來獲取應用程序中的用戶、角色及權限信息的。 * * @param authenticationToken 用戶身份信息 token * @return 返回封裝了用戶信息的 AuthenticationInfo 實例 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("————身份認證方法————"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; // 從數據庫獲取對應用戶名密碼的用戶 String password = "123456"; //password=userService.findPassword(...); String userName= token.getUsername(); String loinPassword=new String(token.getPassword()); System.out.println("userName="+userName+" password="+password); if (null == password) { throw new AccountException("用戶名不正確"); } else if (!password.equals(loinPassword)) { throw new AccountException("密碼不正確"); } return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName()); } /** * 獲取受權信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("————權限認證————"); String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //得到該用戶角色 String role = "admin"; //role=userService.getRole(...); Set<String> set = new HashSet<>(); //須要將 role 封裝到 Set 做爲 info.setRoles() 的參數 set.add(role); //設置該用戶擁有的角色 info.setRoles(set); return info; } }
1.重寫的兩個方法分別是實現身份認證以及權限認證,shiro 中有個做登錄操做的 Subject.login() 方法,當咱們把封裝了用戶名,密碼的 token 做爲參數傳入,便會跑進這兩個方法裏面(不必定兩個方法都會進入)springboot
其中 doGetAuthorizationInfo 方法只有在須要權限認證時纔會進去,好比前面配置類中配置了 filterChainDefinitionMap.put("/admin/**", "roles[admin]"); 的管理員角色,這時訪問 /admin/** 時就會進入 doGetAuthorizationInfo 方法來檢查權限;app
而 doGetAuthenticationInfo 方法則是須要身份認證時(好比前面的 Subject.login() 方法)纔會進入jsp
2.UsernamePasswordToken 類,咱們能夠從該對象拿到登錄時的用戶名和密碼(由於登錄時會使用 new UsernamePasswordToken(username, password)
)ide
注意
有不少人會發現,UserService 等類,接口沒法經過 @Autowired 注入進來,跑程序的時候會報 NullPointerException,網上說了不少諸如是 Spring 加載順序等緣由,但其實有一個很重要的地方要你們注意,CustomRealm 這個類是在 shiro 配置類的 securityManager.setRealm() 方法中設置進去的,而不少人直接寫securityManager.setRealm(new CustomRealm()); ,這樣是不行的,必需要使用 @Bean 注入 MyRealm,不能直接 new 對象
第四步、測試類
@RestController public class LoginController { @Autowired private ResultMap resultMap; @Autowired private UserMapper userMapper; @RequestMapping(value = "/notLogin", method = RequestMethod.GET) public ResultMap notLogin() { return resultMap.success().message("您還沒有登錄!"); } @RequestMapping(value = "/notRole", method = RequestMethod.GET) public ResultMap notRole() { return resultMap.success().message("您沒有權限!"); } @RequestMapping(value = "/logout", method = RequestMethod.GET) public ResultMap logout() { Subject subject = SecurityUtils.getSubject(); //註銷 subject.logout(); return resultMap.success().message("成功註銷!"); } /** * 登錄 * * @param username 用戶名 * @param password 密碼 */ @RequestMapping(value = "/login", method = RequestMethod.POST) public ResultMap login(String username, String password) { // 從SecurityUtils裏邊建立一個 subject Subject subject = SecurityUtils.getSubject(); // 在認證提交前準備 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 執行認證登錄 subject.login(token); //根據權限,指定返回數據 String role = userMapper.getRole(username); if ("user".equals(role)) { return resultMap.success().message("歡迎登錄"); } if ("admin".equals(role)) { return resultMap.success().message("歡迎來到管理員頁面"); } return resultMap.fail().message("權限錯誤!"); } }
普通登錄用戶 @RestController @RequestMapping("/user") public class UserController{ @Autowired private final ResultMap resultMap; @RequestMapping(value = "/getMessage", method = RequestMethod.GET) public ResultMap getMessage() { return resultMap.success().message("您擁有用戶權限,能夠得到該接口的信息!"); } }
(1)@RequiresPermissions註解失效
在shiroConfig中加入以下內容
/** * 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 * 配置如下兩個bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)便可實現此功能 * * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }
(2)獲取當前登陸用戶的信息
修改 CustomRealm.doGetAuthenticationInfo( ) 最後一行代碼
return new SimpleAuthenticationInfo(new Principal(id+"",userName,password), password, getName());
此處 Principal 是咱們自定義的類,以下:
public static class Principal implements Serializable { private String id; private String userName; private String passWord; public Principal(String id, String userName, String passWord) { this.id = id; this.userName = userName; this.passWord = passWord; } //.....省略set..get..
再封裝一個 UserUtil,用於獲取用戶信息
public class UserUtil { public static boolean isLogin(){ CustomRealm.Principal principal=getPrincipal(); if(principal==null|| StringUtils.isEmpty(principal.getId())){ return false; } return true; } public static User getUser(){ if(isLogin()==false){ return null; } CustomRealm.Principal principal=getPrincipal(); User user=new User(); user.setId(principal.getId()); user.setUserName(principal.getUserName()); user.setPassWord(principal.getPassWord()); return user; } public static CustomRealm.Principal getPrincipal(){ org.apache.shiro.subject.Subject subject= SecurityUtils.getSubject(); CustomRealm.Principal principal= (CustomRealm.Principal) subject.getPrincipal(); return principal; } }
(3)更新當前登陸用戶的信息
拿到咱們自定義的Principal對象,直接修改其中的屬性就能夠
CustomRealm.Principal principal= (CustomRealm.Principal) SecurityUtils.getSubject().getPrincipal(); principal.setUserName("張三丰");