寫在前面
通過前幾篇文章的學習,我們從大體上了解了shiro關於認證和授權方面的應用。在接下來的文章當中,我將通過一個demo,帶領大家搭建一個SpringBoot整合Shiro的一個專案開發腳手架,將之前學過的知識點串到一起,其中,也會補充一些之前沒有講過的內容。通過這個demo結束這幾天的學習,同時也是結束國慶中秋小長假shiro系列專題入門文章。
SpringBoot整合Shiro思路分析
鑑權流程分析
我們將我們的SpringBoot應用整合shiro,主要目的就是讓shiro幫我們處理認證和授權的相關內容。也就是說,我們需要讓shiro接管我們SpringBoot應用的會話。讓使用者的每一次請求都經過shiro進行認證和授權。因此,我們需要將使用者請求攔截下來轉發給shiro處理,這個攔截器是shiro提供的,ShiroFilter
。
步驟:
-
使用者通過客戶端(瀏覽器、手機App、小程式)發起請求
-
ShiroFilter攔截請求並判斷請求訪問的資源是否為受保護資源:
2.1 是,則執行步驟3
2.2 不是,則直接放行
-
判斷使用者是否已通過認證:
3.1 是 ,則執行步驟4
3.2 否,將使用者請求重定向到認證頁面,讓使用者先認證
-
獲取使用者許可權資訊和訪問資源所需要的許可權資訊進行比對:
4.1 使用者具備訪問許可權,則放行
4.2 使用者不具備許可權,返回403的相應提示
資料庫分析設計
我們通過MySQL儲存我們的認證和許可權的相關資料。採用使用者-角色-許可權模型實現動態管理使用者許可權資訊。
我們將系統當中的選單、按鈕、後端介面都抽象成系統的資源資料。以下是資料庫表的設計:
文末提供sql指令碼的下載。
整合步驟
環境搭建
maven
建立一個SpringBoot的web應用,並引入如下依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.6.0</version>
</dependency>
新增對使用者、角色和資源的CRUD支援
這裡程式碼就省略了,不影響理解,完整程式碼可以從文末提供的方式中下載。
配置Shiro
自定義Realm
/**自定義Realm,使用mysql資料來源
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/6 9:09
*/
public class MySQLRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Autowired
private IRoleService roleService;
@Autowired
private IResourceService resourceService;
/**
* 授權
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
List<Role> roleList = roleService.findByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (Role role : roleList) {
authorizationInfo.addRole(role.getRoleName());
}
List<Long> roleIdList = new ArrayList<>();
for (Role role : roleList) {
roleIdList.add(role.getRoleId());
}
List<Resource> resourceList = resourceService.findByRoleIds(roleIdList);
for (Resource resource : resourceList) {
authorizationInfo.addStringPermission(resource.getResourcePermissionTag());
}
return authorizationInfo;
}
/**
* 認證
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if(token==null){
return null;
}
String principal = (String) token.getPrincipal();
User user = userService.findByUsername(principal);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return simpleAuthenticationInfo;
}
}
shiro中的Realm物件充當了認證、授權數資訊的據源作用。關於更多自定義Realm的內容請參考我的另一篇文章《Shiro入門學習---使用自定義Realm完成認證|練氣中期》 。
ShiroConfig
/**shiro配置類
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/6 9:11
*/
@Configuration
public class ShiroConfig {
/**
* 建立ShiroFilter攔截器
* @return ShiroFilterFactoryBean
*/
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置不攔截路徑和攔截路徑,順序不能反
HashMap<String, String> map = new HashMap<>(5);
map.put("/authc/**","anon");
map.put("/login.html","anon");
map.put("/js/**","anon");
map.put("/css/**","anon");
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//覆蓋預設的登入url
shiroFilterFactoryBean.setLoginUrl("/authc/unauthc");
return shiroFilterFactoryBean;
}
@Bean
public Realm getRealm(){
//設定憑證匹配器,修改為hash憑證匹配器
HashedCredentialsMatcher myCredentialsMatcher = new HashedCredentialsMatcher();
//設定演算法
myCredentialsMatcher.setHashAlgorithmName("md5");
//雜湊次數
myCredentialsMatcher.setHashIterations(512);
MySQLRealm realm = new MySQLRealm();
realm.setCredentialsMatcher(myCredentialsMatcher);
return realm;
}
/**
* 建立shiro web應用下的安全管理器
* @return DefaultWebSecurityManager
*/
@Bean
public DefaultWebSecurityManager getSecurityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
}
在編寫shiro配置類這一步,需要大家注意的是,因為我們使用的是md5+salt+hash加密我們的密碼,因此要換掉預設的憑證匹配器CredentialsMatcher物件,對於這部分的內容請參考我的另一篇文章《shiro入門學習--使用MD5和salt進行加密|練氣後期》 。
實現認證模組
VO層
/**認證請求引數
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/7 15:12
*/
@Data
public class LoginVO implements Serializable {
private String username;
private String password;
}
web層
/**認證模組
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/6 10:07
*/
@RestController
@RequestMapping("/authc")
public class AuthcController {
@Autowired
private AuthcService authcService;
@PostMapping("/login")
public boolean login(@RequestBody LoginVO loginVO){
return authcService.login(loginVO);
}
@GetMapping("/unauthc")
public String unauthc(){
return "請先登入";
}
}
service層
/**
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/7 15:15
*/
@Service
public class AuthcServiceImpl implements AuthcService {
@Override
public boolean login(LoginVO loginVO) throws AuthenticationException {
if (loginVO==null){
return false;
}
if (loginVO.getUsername()==null||"".equals(loginVO.getUsername())){
return false;
}
if (loginVO.getPassword() == null || "".equals(loginVO.getPassword())){
return false;
}
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(loginVO.getUsername(), loginVO.getPassword());
subject.login(token);
return true;
}
}
實現產品模組
/**產品模組
* @author 賴柄灃 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/6 10:14
*/
@RestController
@RequestMapping("/product")
public class ProductController {
@RequiresPermissions("product:get")
@GetMapping("/get/list")
public String getProductList() {
return "productList";
}
@RequiresPermissions("product:delete")
@GetMapping("/delete")
public String deleteProduct() {
return "刪除產品資料";
}
}
對於註解實現訪問控制,shiro主要有兩個註解:RequiresPermissions和RequiresRoles。均可以用在類和方法上。具體用在哪可以根據自己的系統許可權劃分粒度決定。
對於這兩個註解,有兩個引數:
value
:分別對應permission的許可權字串值和role的角色名稱;logical
:邏輯運算子。這是一個列舉型別,有AND
和OR
兩個值。當使用AND
時表示需要滿足所有傳入的value
值,OR
表示僅需滿足一個value
即可。預設為AND
關於shiro許可權(訪問控制)的更多內容,可以閱讀我的另一篇文章《shiro入門學習--授權(Authorization)|築基初期》
簡單測試
認證通過的情況
認證未通過的情況
獲取產品資訊
請求沒有訪問許可權的資源
預設的訊息提示可以換一下。
未經過認證直接訪問受保護資源
寫在最後
在這一篇文章當中,我們搭建了一個初步的SpringBoot整合Shiro的應用,實現了認證和授權。
在下一篇文章當中,我們將接著完善這個小demo。加入自定義shiro會話管理和shiro快取的內容。並且會將這個小demo進行升級,使其變成前後端分離的模式。
雙節即將結束,程式碼人在江湖!
如果您覺得這篇文章能給您帶來幫助,那麼可以點贊鼓勵一下。如有錯誤之處,還請不吝賜教。在此,謝過各位鄉親父老!
程式碼及sql下載方式:微信搜尋【Java開發實踐】,加關注並回復20201007
即可獲取下載連結。