基於springboot的授權操作
首先而言,要進行授權,就是要實現看人下菜碟這個操作,即賦予成員相應的角色,針對不同的角色,可以訪問相應的頁面,使用相應的資源,首先,在數據表設計時,每個成員具有一個role屬性,指定這個成員所屬的角色,角色在對應一個授權表,完成角色和權限的一對多關係,下面是數據表的結構
customer:
下面是role,比較簡單:
接下來是權限表:
在這個表中使用一個role_id完成和角色的對應,使得角色和權限完成一對多的映射
聲明:以上數據表結構設計存在一些冗餘和缺陷,然學習期間,一切從簡
接下來我們需要進行授權,其實授權,說白了就是,資源的攔截和放行,在第一季已經做過,shiro總共有四種認證過濾器,第一季我們用了無需認證的anon和需要登陸才能進行操作的authc,下面可以使用perms進行授權,首先要進行資源的權限字符串的對應,這會精確到某個controller的某個方法,例如下面這個controller:
package com.example.lclepro.Controller;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
UserService userService;
@PostMapping("/login")
public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
ModelAndView mv = new ModelAndView();
//獲取到Subject對象
Subject suject = SecurityUtils.getSubject();
//將收集到的用戶名和密碼存儲到token中
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//執行登入方法
try {
suject.login(token);
mv.setViewName("pages/customer");
}
catch (UnknownAccountException e){
//用戶名不存在時拋出的異常
mv.addObject("msg", "用戶不存在,請找到合適的介紹人加入組織(๑•̀ㅂ•́)و✧");
mv.setViewName("pages/index");
}
catch (IncorrectCredentialsException e){
//用戶名密碼不匹配時拋出的異常
mv.addObject("msg", "用戶名與密碼不匹配( ̄_, ̄ )");
mv.setViewName("pages/index");
}
return mv;
}
//使用ajax進行數據交互的方法,需要加上@ResponseBody註解
@GetMapping("/getAllCustomers")
@ResponseBody
public Msg getAllCustomers(){
return userService.getAllCustomers();
}
}
如果要對方法/getAllCustomers進行授權,就可以在自定義的Realm中完成授權方法如下
package com.example.lclepro.Config;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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 org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//實現攔截器
//@Bean註解註釋在方法上,將返回的實體類交給容器處理,id是方法名
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//設置攔截成功以後,訪問的頁面
shiroFilterFactoryBean.setLoginUrl("/");
//攔截器Map的創建
Map<String, String> map = new HashMap<String, String>();
//針對不同的url,使用不同的攔截器,放到map中,即可生效,完成攔截
//攔截所有請求
map.put("/*", "authc");
//放行登陸請求
map.put("/", "anon");
//放行登錄認證請求
map.put("/user/login", "anon");
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
//設置相關的map集合,完成攔截功能的收集
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//實現web安全管理器的加載
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//實現Realm的加載
@Bean
public LcleRealm lcleRealm(){
return new LcleRealm();
}
//要加入註解,指定方法的攔截,則需要加入以下的三個方法,開啓註解模式
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 開啓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(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
注意這一條代碼:
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
我們將,剛纔那個方法,映射出了一個授權字符串即中括號裏面的內容“user:getAllCustomers”,這時,用戶進行訪問,由於未進行授權,請求則會被攔截:
注意看第二個401,這就是我們請求的方法,他由於沒有被授權,請求失敗了,那麼,如何進行授權操作呢,我們不妨在自定義的Realm中加上一句授權代碼:
package com.example.lclepro.Config;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Service.UserService;
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.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class LcleRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//執行授權邏輯
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Subject subject = SecurityUtils.getSubject();
LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
System.out.println(customer.getUsername()+"開始執行授權邏輯!");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:getAllCustomers");
return info;
}
//執行認證邏輯,執行login()方法以後,會進入到這裏,進行用戶名密碼的比對
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//執行認證邏輯
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//獲取密碼
String password = userService.isUser(token.getUsername());
if(password==null){
throw new UnknownAccountException();
}
else{
LcleCustomer customer = userService.selectCustomerByName(token.getUsername());
return new SimpleAuthenticationInfo(customer, password, "");
}
}
}
注意這個授權方法中的代碼`
Subject subject = SecurityUtils.getSubject();
LcleCustomer customer = (LcleCustomer)subject.getPrincipal();
System.out.println(customer.getUsername()+"開始執行授權邏輯!");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:getAllCustomers");
return info;
我們先從shiro中獲取到,我們本次操作的對象,然後定義一個授權信息相關的String(多個授權也可以使用list),,如此我們就完成了簡單的授權,重新啓動頁面,進行測試
我們看到,原先的401已經消失,資源訪問正常,但是這樣做未免會很傻,因爲一個正常的項目,硬編碼進去如此多的映射url,未免有些難受(儘管用戶的權限可以持久化到數據庫),這時候就體現出了註解的重要性,能否通過註解的方式,對每個controller的方法進行標記相應的授權url呢,答案是肯定的,不過我們的shiroConfig類需要做出變動,用來開啓註解:
package com.example.lclepro.Config;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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 org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//實現攔截器
//@Bean註解註釋在方法上,將返回的實體類交給容器處理,id是方法名
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//設置攔截成功以後,訪問的頁面
shiroFilterFactoryBean.setLoginUrl("/");
//攔截器Map的創建
Map<String, String> map = new HashMap<String, String>();
//針對不同的url,使用不同的攔截器,放到map中,即可生效,完成攔截
//攔截所有請求
map.put("/*", "authc");
//放行登陸請求
map.put("/", "anon");
//放行登錄認證請求
map.put("/user/login", "anon");
map.put("/user/getAllCustomers", "perms[user:getAllCustomers]");
//設置相關的map集合,完成攔截功能的收集
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//實現web安全管理器的加載
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("lcleRealm") LcleRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//實現Realm的加載
@Bean
public LcleRealm lcleRealm(){
return new LcleRealm();
}
//要加入註解,指定方法的攔截,則需要加入以下的三個方法,開啓註解模式
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 開啓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(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
和之前相比,我們在最後加了三個方法,用來開啓註解,那麼我們接下來可以在每個方法頭上加上一個註解@RequiresPermissions,用來標記這個方法的授權字符串,下面是controller的代碼
package com.example.lclepro.Controller;
import com.example.lclepro.Bean.LcleCustomer;
import com.example.lclepro.Bean.Msg;
import com.example.lclepro.Service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
@RequestMapping(“user”)
public class UserController {
@Autowired
UserService userService;
@PostMapping("/login")
public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password){
ModelAndView mv = new ModelAndView();
//獲取到Subject對象
Subject suject = SecurityUtils.getSubject();
//將收集到的用戶名和密碼存儲到token中
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//執行登入方法
try {
suject.login(token);
mv.setViewName("pages/customer");
}
catch (UnknownAccountException e){
//用戶名不存在時拋出的異常
mv.addObject("msg", "用戶不存在,請找到合適的介紹人加入組織(๑•̀ㅂ•́)و✧");
mv.setViewName("pages/index");
}
catch (IncorrectCredentialsException e){
//用戶名密碼不匹配時拋出的異常
mv.addObject("msg", "用戶名與密碼不匹配( ̄_, ̄ )");
mv.setViewName("pages/index");
}
return mv;
}
//使用ajax進行數據交互的方法,需要加上@ResponseBody註解
@RequiresPermissions("user:getAllCustomers")
@GetMapping("/getAllCustomers")
@ResponseBody
public Msg getAllCustomers(){
return userService.getAllCustomers();
}
}
至此,簡單的授權已經完成,shiro的工作也基本完成,要想完成多個權限的操作,不妨將權限和角色進行映射,再將每個人賦予不同的角色,然後在自定義的Realm中使用Service方法,獲取當前用戶的授權字符串,逐個加入info中,完成授權
爲之則易,不爲則難