文末加了源碼地址,報錯的朋友可以去下載
---更新于2020/06/15
應(yīng)廣大網(wǎng)友要求本次pom文件代碼貼全了莫绣,
springboot版本用了目前最新的2.3.4.RELEASE,
shiro版本用了目前最新的1.6.0
親測有效,并將持續(xù)更新
---更新于2020/09/20
添加了shiro的一些常見的異常
---更新于2020/09/20
1.shiro是什么?
Shiro是Apache下的一個開源項目捏检。shiro屬于輕量級框架互纯,相對于SpringSecurity簡單的多遭庶,也沒有SpringSecurity那么復(fù)雜。以下是我自己學(xué)習(xí)之后的記錄蓖乘。
官方架構(gòu)圖如下:
2.主要功能
shiro主要有三大功能模塊:
1. Subject:主體锤悄,一般指用戶。
2. SecurityManager:安全管理器嘉抒,管理所有Subject零聚,可以配合內(nèi)部安全組件。(類似于SpringMVC中的DispatcherServlet)
3. Realms:用于進(jìn)行權(quán)限信息的驗證些侍,一般需要自己實現(xiàn)隶症。
3.細(xì)分功能
1. Authentication:身份認(rèn)證/登錄(賬號密碼驗證)。
2. Authorization:授權(quán)岗宣,即角色或者權(quán)限驗證沿腰。
3. Session Manager:會話管理,用戶登錄后的session相關(guān)管理狈定。
4. Cryptography:加密颂龙,密碼加密等。
5. Web Support:Web支持纽什,集成Web環(huán)境措嵌。
6. Caching:緩存,用戶信息芦缰、角色企巢、權(quán)限等緩存到如redis等緩存中。
7. Concurrency:多線程并發(fā)驗證让蕾,在一個線程中開啟另一個線程浪规,可以把權(quán)限自動傳播過去。
8. Testing:測試支持探孝;
9. Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進(jìn)行訪問笋婿。
10. Remember Me:記住我,登錄后顿颅,下次再來的話不用登錄了缸濒。
(更多關(guān)于shiro是什么的文字請自行去搜索引擎找,本文主要記錄springboot與shiro的集成)
首先先創(chuàng)建springboot項目,此處不過多描述庇配。
上代碼:
目錄結(jié)構(gòu):
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.wsl</groupId>
<artifactId>spring-shiro-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-shiro-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring.shiro.version>1.6.0</spring.shiro.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${spring.shiro.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--頁面模板依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--熱部署依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
user.java(用戶實體類):
package com.wsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
@Data
@AllArgsConstructor
public class User {
private String id;
private String userName;
private String password;
/**
* 用戶對應(yīng)的角色集合
*/
private Set<Role> roles;
}
Role.java(角色對應(yīng)實體類):
package com.wsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
@Data
@AllArgsConstructor
public class Role {
private String id;
private String roleName;
/**
* 角色對應(yīng)權(quán)限集合
*/
private Set<Permissions> permissions;
}
Permissions.java(權(quán)限對應(yīng)實體類):
package com.wsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Permissions {
private String id;
private String permissionsName;
}
LoginServiceImpl.java:
package com.wsl.service.impl;
import com.wsl.bean.Permissions;
import com.wsl.bean.Role;
import com.wsl.bean.User;
import com.wsl.service.LoginService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Service
public class LoginServiceImpl implements LoginService {
@Override
public User getUserByName(String getMapByName) {
return getMapByName(getMapByName);
}
/**
* 模擬數(shù)據(jù)庫查詢
*
* @param userName 用戶名
* @return User
*/
private User getMapByName(String userName) {
Permissions permissions1 = new Permissions("1", "query");
Permissions permissions2 = new Permissions("2", "add");
Set<Permissions> permissionsSet = new HashSet<>();
permissionsSet.add(permissions1);
permissionsSet.add(permissions2);
Role role = new Role("1", "admin", permissionsSet);
Set<Role> roleSet = new HashSet<>();
roleSet.add(role);
User user = new User("1", "wsl", "123456", roleSet);
Map<String, User> map = new HashMap<>();
map.put(user.getUserName(), user);
Set<Permissions> permissionsSet1 = new HashSet<>();
permissionsSet1.add(permissions1);
Role role1 = new Role("2", "user", permissionsSet1);
Set<Role> roleSet1 = new HashSet<>();
roleSet1.add(role1);
User user1 = new User("2", "zhangsan", "123456", roleSet1);
map.put(user1.getUserName(), user1);
return map.get(userName);
}
}
自定義Realm用于查詢用戶的角色和權(quán)限信息并保存到權(quán)限管理器:
CustomRealm.java
package com.wsl.shiro;
import com.wsl.bean.Permissions;
import com.wsl.bean.Role;
import com.wsl.bean.User;
import com.wsl.service.LoginService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 org.springframework.util.StringUtils;
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* @MethodName doGetAuthorizationInfo
* @Description 權(quán)限配置類
* @Param [principalCollection]
* @Return AuthorizationInfo
* @Author WangShiLin
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//獲取登錄用戶名
String name = (String) principalCollection.getPrimaryPrincipal();
//查詢用戶名稱
User user = loginService.getUserByName(name);
//添加角色和權(quán)限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//添加權(quán)限
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
}
}
return simpleAuthorizationInfo;
}
/**
* @MethodName doGetAuthenticationInfo
* @Description 認(rèn)證配置類
* @Param [authenticationToken]
* @Return AuthenticationInfo
* @Author WangShiLin
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
//獲取用戶信息
String name = authenticationToken.getPrincipal().toString();
User user = loginService.getUserByName(name);
if (user == null) {
//這里返回后會報出對應(yīng)異常
return null;
} else {
//這里驗證authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
ShiroConfig.java斩跌,把CustomRealm和SecurityManager等注入到spring容器中:
package com.wsl.config;
import com.wsl.shiro.CustomRealm;
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class shiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//將自己的驗證方式加入容器
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
//權(quán)限管理,配置主要是Realm的管理認(rèn)證
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工廠捞慌,設(shè)置對應(yīng)的過濾條件和跳轉(zhuǎn)條件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//對所有用戶認(rèn)證
map.put("/**", "authc");
//登錄
shiroFilterFactoryBean.setLoginUrl("/login");
//首頁
shiroFilterFactoryBean.setSuccessUrl("/index");
//錯誤頁面耀鸦,認(rèn)證不通過跳轉(zhuǎn)
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
LoginController.java:我們編寫一個簡單的登錄方法,一個index頁的查詢方法啸澡,一個add方法揭糕,一個admin方法,對應(yīng)不同的角色或權(quán)限攔截
package com.wsl.controller;
import com.wsl.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class LoginController {
@GetMapping("/login")
public String login(User user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "請輸入用戶名和密碼锻霎!";
}
//用戶認(rèn)證信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
);
try {
//進(jìn)行驗證著角,這里可以捕獲異常,然后返回對應(yīng)信息
subject.login(usernamePasswordToken);
// subject.checkRole("admin");
// subject.checkPermissions("query", "add");
} catch (UnknownAccountException e) {
log.error("用戶名不存在旋恼!", e);
return "用戶名不存在吏口!";
} catch (AuthenticationException e) {
log.error("賬號或密碼錯誤!", e);
return "賬號或密碼錯誤冰更!";
} catch (AuthorizationException e) {
log.error("沒有權(quán)限产徊!", e);
return "沒有權(quán)限";
}
return "login success";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin success!";
}
@RequiresPermissions("query")
@GetMapping("/index")
public String index() {
return "index success!";
}
@RequiresPermissions("add")
@GetMapping("/add")
public String add() {
return "add success!";
}
}
注解驗證角色和權(quán)限的話無法捕捉異常,從而無法正確的返回給前端錯誤信息蜀细,所以我加了一個類用于攔截異常舟铜,具體代碼如下
MyExceptionHandler.java
package com.wsl.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("沒有通過權(quán)限驗證!", e);
return "沒有通過權(quán)限驗證奠衔!";
}
}
打開網(wǎng)頁 http://localhost:8080/login?userName=wsl&password=123456
然后輸入index地址:http://localhost:8080/index
換zhangsan賬號登錄后再訪問index
http://localhost:8080/login?userName=zhangsan&password=123456
http://localhost:8080/add
最近看好多人都項目報錯谆刨,我把源碼放到碼云上,大家可以自行下載:
源碼地址
最后加一些常見的shiro異常:
1. AuthenticationException 認(rèn)證異常
Shiro在登錄認(rèn)證過程中归斤,認(rèn)證失敗需要拋出的異常痊夭。 AuthenticationException包含以下子類:
1.1. CredentitalsException 憑證異常
IncorrectCredentialsException 不正確的憑證
ExpiredCredentialsException 憑證過期
1.2. AccountException 賬號異常
ConcurrentAccessException: 并發(fā)訪問異常(多個用戶同時登錄時拋出)
UnknownAccountException: 未知的賬號
ExcessiveAttemptsException: 認(rèn)證次數(shù)超過限制
DisabledAccountException: 禁用的賬號
LockedAccountException: 賬號被鎖定
UnsupportedTokenException: 使用了不支持的Token
2. AuthorizationException: 授權(quán)異常
Shiro在登錄認(rèn)證過程中,授權(quán)失敗需要拋出的異常脏里。 AuthorizationException包含以下子類:
2.1. UnauthorizedException:
拋出以指示請求的操作或?qū)φ埱蟮馁Y源的訪問是不允許的她我。
2.2. UnanthenticatedException:
當(dāng)尚未完成成功認(rèn)證時,嘗試執(zhí)行授權(quán)操作時引發(fā)異常迫横。