Shiro要點概覽與SpringBoot整合實例

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,然後實現:

  1. doGetAutherizationInfo(PrincipalCollection principals)用於授權
  2. 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,或者:

Shiro過濾器

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權限

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章