1. 簡介
概念 | 說明 |
---|---|
Subject | 主體,簡化點說就是用戶實體 |
Principal | Subject的唯一標識,如id、用戶名、手機號、郵箱等 |
Credential | 憑證信息,主體證明自己的東西,如密碼、證書等 |
Authenticator | 認證器,對Subject身份進行認證,例如驗證用戶的用戶名和密碼是否匹配 |
Authorizer | 授權器,通過認證器認證之後,要訪問資源,還得獲得資源授權 |
sessionManager | 會話管理,不依賴web容器的session,可以將分佈式應用的會話集中在一點管理,實現單點登錄,自定義可繼承DefaultWebSessionManager |
SecurityManager | 安全管理器,繼承了Authenticator,Authorizer, SessionManager,對全部的subject進行安全管理 |
Realm | 領域,實際認證和授權的地方,因爲shiro不知道你的用戶和密碼相關數據,所以一般需要自定義Realm完成認證和授權 |
SessionDAO | 對session會話操作的一套接口,自定義可繼承EnterpriseCacheSessionDAO |
CacheManager | 緩存管理,將用戶權限數據存儲在緩存,避免每次訪問數據庫 |
Cryptography | 密碼管理,加密/解密的組件,如:常用的散列、加解密等功能 |
2. AuthorizingRealm
對於應用來說,Shiro的侵入雖然比較高,但是使用還是相對比較簡單,如果不想太麻煩,只需要繼承AuthorizingRealm,然後實現:
- doGetAutherizationInfo(PrincipalCollection principals)用於授權
- doGetAuthenticationInfo(AuthenticationToken token)用於認證
然後配置一下ShiroFilterFactoryBean,告訴Shiro資源的權限就可以。
3. 默認Filter
Shiro通過一系列filter來控制訪問權限,並預先定義了很多過濾器(org.apache.shiro.web.filter.mgt.DefaultFilter),常用的過濾器配置如下:
字符串 | 說明 |
---|---|
anon | 所有用戶可訪問 |
authc | 認證用戶可訪問 |
user | 特定用戶能訪問 |
port | 特定端口能訪問,/user/**=port[8088] |
http | 特定http能訪問,/user/**=perms[user:post] |
roles | 指定角色用戶可訪問,/admins/=roles[admin],admins/**=roles["admin,sys"] |
perms | 指定權限用戶可訪問,/admins/=perms[user:add:],/admins/**=perms["user:add:,user:modify:*"] |
沒有登錄之前都跳轉登錄頁面,登錄之後檢查權限,沒有權限跳轉未授權頁面
4. 自定義Filter
自定義Filter可以繼承AuthorizationFilter,當然也可以繼承AccessControlFilter,或者:
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class OnlineSessionFilter extends AccessControlFilter {
/**
* 是否允許訪問
* @mappedValue [urls]配置中攔截器參數部分
* @return 如果:true允許訪問,否則不允許訪問
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 檢查邏輯
return true;
}
/**
* 當訪問拒絕時是否繼續處理
* @return 如果:true表示需要繼續處理,否則直接返回
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (subject != null) {
subject.logout();
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
然後在就可以這樣使用:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMap = new HashMap<>();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
// 登錄成功後要跳轉的鏈接
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
// 訪問未授權頁面之後跳轉鏈接
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauthc");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", new OnlineSessionFilter());//自定義過濾器
shiroFilterFactoryBean.setFilters(filters);
// 所有請求需要認證
filterChainDefinitionMap.put("/**", "onlineSession");
filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/shiro/index", "authc");
filterChainDefinitionMap.put("/shiro/admin", "roles[admin]");
filterChainDefinitionMap.put("/shiro/insertUpdate", "perms[create,update]");
filterChainDefinitionMap.put("/shiro/delete", "perms[delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
5. SimpleCredentialsMatcher
因爲密碼是直接取得數據庫的密碼,所以想要保證用戶傳入的密碼能和我的密碼能夠匹配,需要創建自定義的密碼校驗規則。
可以繼承SimpleCredentialsMatcher來實現自己的憑證驗證,不過一般都使用HashedCredentialsMatcher,例如Sha256CredentialsMatcher。
6. 註解
註解 | 說明 |
---|---|
@RequiresAuthentication | 驗證用戶是否登錄 |
@RequiresUser | 驗證用戶是否登錄,和@RequiresAuthentication不同記住我的用戶也算 |
@RequiresGuest | 驗證是否是一個遊客,沒有登錄 |
@RequiresRoles | 必須是指定角色@RequiresRoles("admin"),@RequiresRoles(value={"admin", "sys"}, logical=Logical.OR) |
@RequiresPermissions | 必須有指定權限@RequiresPermissions({"delete", "write"}) |
7. 實例
如果要簡單使用shiro,還是比較容易,下面給一個實例。
7.1 maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.7.0</version>
</dependency>
省略了SpringBoot相關的,選擇自己喜歡的版本添加,shiro-spring1.7.0的shiro-ehcache現在暫時要自己單獨添加一下,我看了它的配置是有,但是在自己項目中引用不了,所以手動添加一下。
7.2 屬性文件配置
logging.config=classpath:logback.xml
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/data?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username = tim
spring.datasource.password = 123456
spring.jpa.database=MySQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
7.3 shiro緩存配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="mycache-manager" updateCheck="false">
<!-- 磁盤緩存位置 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默認緩存 -->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
<cache name="shiro"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="60"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="true">
</cache>
</ehcache>
7.4 實體類
用戶類:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
@Column(unique = true,length = 20)
private String username;
@Column(length = 64)
private String password;
private String salt;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {@JoinColumn(name = "rid") })
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getCredentialsSalt() {
return username + salt + salt;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + "]";
}
}
角色類:
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue
private Integer id;
private String role;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_permission", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {@JoinColumn(name = "pid") })
private List<Permission> permissions;
@ManyToMany
@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {@JoinColumn(name = "uid") })
private List<User> users;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
權限類:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "permission")
public class Permission {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany
@JoinTable(name = "role_permission", joinColumns = { @JoinColumn(name = "pid") }, inverseJoinColumns = {@JoinColumn(name = "rid") })
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
7.5 Repository
import org.springframework.data.repository.CrudRepository;
import vip.mycollege.mysql.jpa.entity.User;
public interface UserRepository extends CrudRepository<User,String> {
User findUserByUsername(String username);
}
7.6 Realm
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.apache.shiro.util.ByteSource;
import vip.mycollege.mysql.jpa.entity.Permission;
import vip.mycollege.mysql.jpa.entity.Role;
import vip.mycollege.mysql.jpa.entity.User;
import vip.mycollege.mysql.jpa.repository.UserRepository;
import javax.annotation.Resource;
public class UserPassRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
/**
* 登錄
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userRepository.findUserByUsername(username);//從數據庫查找username的用戶
if (user == null) {
return null;
}
ByteSource salt = ByteSource.Util.bytes(user.getCredentialsSalt());
String realmName = this.getClass().getName();
// 有鹽值的認證
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, user.getPassword(), salt, realmName);
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),realmName);
return authenticationInfo;
}
/**
* 授權
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String username = (String) principals.getPrimaryPrincipal();
User user = userRepository.findUserByUsername(username);//從數據庫獲取權限信息
if(user == null){
return null;
}
for (Role role : user.getRoles()) {
authorizationInfo.addRole(role.getRole());
for (Permission permission : role.getPermissions()) {
authorizationInfo.addStringPermission(permission.getName());
}
}
return authorizationInfo;
}
}
7.7 配置類
import com.google.common.io.Resources;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
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 vip.mycollege.shiro.CredentialMatcher;
import vip.mycollege.shiro.OnlineSessionFilter;
import vip.mycollege.shiro.UserPassRealm;
import javax.servlet.Filter;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public Authorizer authorizer() {
return new ModularRealmAuthorizer();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMap = new HashMap<>();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
// 登錄成功後要跳轉的鏈接
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
// 訪問未授權頁面之後跳轉鏈接
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauthc");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", new OnlineSessionFilter());
shiroFilterFactoryBean.setFilters(filters);//設置自定義過濾器
// 所有請求需要認證
filterChainDefinitionMap.put("/**", "onlineSession");
filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/shiro/index", "authc");
filterChainDefinitionMap.put("/shiro/admin", "roles[admin]");
filterChainDefinitionMap.put("/shiro/insertUpdate", "perms[printer:insert,update]");
filterChainDefinitionMap.put("/shiro/delete", "perms[printer:delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME); // 散列算法
hashedCredentialsMatcher.setHashIterations(3); // 散列次數
return hashedCredentialsMatcher;
}
/**
* 緩存管理器 使用Ehcache實現
*/
@Bean
public EhCacheManager getEhCacheManager() {
// 先查找是否已經創建了緩存管理器
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("shiro-ehcache");
if(cacheManager == null){//如果沒有就在classpath下查找shiro-ehcache.xml配置文件創建緩存管理器
URL url = Resources.getResource("shiro-ehcache.xml");
if(url == null){
url = Resources.getResource("ehcache/shiro-ehcache.xml");
}
if(url == null){
throw new ConfigurationException("沒有找到shiro-ehcache.xml配置文件");
}
cacheManager = net.sf.ehcache.CacheManager.create(url);
}
EhCacheManager em = new EhCacheManager();
em.setCacheManager(cacheManager);
return em;
}
@Bean("userPassRealm")
public UserPassRealm shiroRealm(EhCacheManager cacheManager) {//配置自定義的權限登錄器
UserPassRealm shiroRealm = new UserPassRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
shiroRealm.setCacheManager(cacheManager);
shiroRealm.setAuthorizationCacheName("shiro");
// shiroRealm.setCredentialsMatcher(credentialMatcher());
return shiroRealm;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("userPassRealm") Realm realm) {//配置核心安全事務管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* cookie 屬性設置
*/
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setDomain("www.mycollege.vip");
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(7 * 24 * 60 * 60);
return cookie;
}
/**
* 記住我
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("xxxxxx"));
return cookieRememberMeManager;
}
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher() { //配置自定義的密碼比較器
return new CredentialMatcher();
}
/**
* 開啓註解支持,方便使用:
*
* @RequiresPermissions
* @RequiresAuthentication
* @RequiresUser
* @RequiresGuest
* @RequiresRoles
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
7.8 Controller
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.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.mycollege.mysql.jpa.entity.User;
import vip.mycollege.mysql.jpa.repository.UserRepository;
import javax.annotation.Resource;
@RestController
@RequestMapping("/shiro")
public class ShiroController {
private static final RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
@Resource
private UserRepository userRepository;
@GetMapping("/index")
public String index() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute("user");
return user.toString();
}
@GetMapping("/admin")
public String admin() {
return "管理員頁面";
}
@GetMapping("/delete")
public Object delete() {
return "需要刪除相關權限頁面";
}
@GetMapping("/insert-update")
public Object insertUpdate() {
return "需要插入更新相關權限頁面";
}
@GetMapping("/login")
public String login() {
return "登錄頁面";
}
@GetMapping("/unauthc")
public String unauthc() {
return "該頁面您還未獲授權,暫時不能訪問";
}
@GetMapping("/do-login")
public Object doLogin(@RequestParam String username, @RequestParam String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 記住我功能
// UsernamePasswordToken token = new UsernamePasswordToken(username, password,true);
//設置session
//SecurityUtils.getSubject().getSession().setAttribute("deployEnv", deployEnv);
//如果用戶已登錄,先踢出
//ShiroSecurityHelper.kickOutUser(username));
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
return "password error!";
} catch (UnknownAccountException uae) {
return "username error!";
}
User user = userRepository.findUserByUsername(username);
subject.getSession().setAttribute("user", user);
return "登錄成功";
}
@GetMapping("/register")
public Object register(@RequestParam("username") String username, @RequestParam("password") String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setSalt(randomNumberGenerator.nextBytes().toHex());
ByteSource byteSourceSalt = ByteSource.Util.bytes(user.getCredentialsSalt());
SimpleHash simpleHash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, byteSourceSalt, 3);
String newPassword = simpleHash.toHex();
user.setPassword(newPassword);
userRepository.save(user);
return "註冊成功";
}
@RequiresPermissions({"printer", "camera"})//默認and
@GetMapping("/requiresPermissions")
public String requiresPermissions(){
return "ok";
}
@RequiresRoles(value={"admin","sys"},logical= Logical.OR)
@GetMapping("/requiresRoles")
public String requiresRoles(){
return "ok";
}
@RequiresGuest
@GetMapping("/requiresGuest")
public String requiresGuest(){
return "ok";
}
@RequiresUser
@GetMapping("/requiresUser")
public String requiresUser(){
return "ok";
}
@RequiresAuthentication
@GetMapping("/requiresAuthentication")
public String requiresAuthentication(){
return "ok";
}
}
7.9 啓動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
啓動之後,會自動創建表,當然數據庫配置要換成你自己的,沒有的過濾器可以刪除掉。
然後,在數據庫中添加一點角色和權限數據就可以通過Controller進行測試了。
8. 測試
# 註冊2個用戶
http://localhost:8080/shiro/register?username=tim&password=123456
http://localhost:8080/shiro/register?username=bob&password=123456
# 遊客訪問
http://localhost:8080/shiro/requiresGuest
# 沒有權限直接拋出異常
http://localhost:8080/shiro/requiresPermissions
# 檢查角色,登錄之後有權限執行
http://localhost:8080/shiro/requiresRoles
# 登錄或者記住我
http://localhost:8080/shiro/requiresUser
# 必須是登錄用戶,用於一下敏感頁面
http://localhost:8080/shiro/requiresAuthentication
# 登錄
http://localhost:8080/shiro/do-login?username=tim&password=123456
更多的校驗可以使用前面的實例自己測試,需要添加user、permssion、role相關數據。
當然,你也可以通過下面的測試類來進行測試:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest
public class ShiroControllerTest {
@Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
@Before
public void setUp(){
this.mockMvc = webAppContextSetup(this.wac).build();
}
@Test
public void index() throws Exception {
mockMvc.perform(get("/shiro/index"))
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void admin() {
}
@Test
public void delete() {
}
@Test
public void insertUpdate() {
}
@Test
public void login() throws Exception {
mockMvc.perform(get("/shiro/login")
.param("username", "tim")
.param("password", "123456")
)
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void unauthc() {
}
@Test
public void doLogin() {
}
@Test
public void register() throws Exception {
mockMvc.perform(get("/shiro/register")
.param("username", "allen")
.param("password", "123456")
)
.andExpect(status().isOk())
.andDo(new ResultHandler() {
@Override
public void handle(MvcResult result) throws Exception {
System.out.println(result.getResponse().getContentAsString());
}
})
.andReturn();
}
@Test
public void requiresPermissions() {
}
@Test
public void requiresRoles() {
}
@Test
public void requiresGuest() {
}
@Test
public void requiresUser() {
}
@Test
public void requiresAuthentication() {
}
}
9. 權限
shiro的角色很好理解,但是權限比較難理解,所以放在最後來簡單介紹一下。
首先:Subject有一個檢查權限的方法:
boolean isPermitted(String permission);
具體實現是在DelegatingSubject:
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
繼續跟進AuthorizingSecurityManager的實現:
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
}
我們可以看到是通過Authorizer授權器來實現,以AuthorizingRealm爲例的實現:
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
可以看到是先獲取到一個PermissionResolver,然後把字符串的permssion解析爲Permission接口,然後通過Permission接口去校驗。
AuthorizingRealm默認實現爲解析出WildCardPermission,所以如果我們不想自己實現權限校驗邏輯,就可以使用WildCardPermission。
問題的關鍵就是:WildCardPermission怎樣校驗權限?
10. WildCardPermission權限
DomainPermission有一個之類DomainPermission。
權限使用字符串定義,分三級,冒號分開:
資源(resource,domain):操作(action):實例(instance,操作在那其上面執行)
permission | 說明 |
---|---|
printer | 所有實例都有資源printer的所有權限,等價於printer:: |
printer:query | 資源上面指定query權限,等價於printer:query.* |
printer:print,query | 資源上面指定多個權限 |
printer:* | 資源上面所用權限 |
*:view | 所有資源都有view權限 |
printer:query:inst32 | 指定實例纔有query權限 |
printer:print:* | 所有實例都有print權限 |
printer:: | 所有實例都有資源printer的所有權限 |
printer:*:inst32 | inst32實例有資源printer的所有權限 |
printer:query,print:inst32 | inst32實例有資源printer的query和print權限 |
省略只能省略最右邊的,資源必須指定。
WildCardPermission權限字符串都是你自己定義設計的,WildCardPermission只會去對比檢查權限是否匹配,而並不關心權限的字符串是什麼。
11. 文檔資料
shiro官網 10分鐘小實例 shiro guide shiro文檔 shiro web shiro授權 shiro權限