Shiro要点概览与SpringBoot整合实例

筅森魡賤 提交于 2020-12-29 22:19:40

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!