项目依赖

<parent>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-parent</artifactId>  
    <version>2.7.13</version>  
    <relativePath/> <!-- lookup parent from repository -->  
</parent>  
<groupId>com.example</groupId>  
<artifactId>springboot-shiro</artifactId>  
<version>0.0.1-SNAPSHOT</version>  
<name>springboot-shiro</name>  
<description>springboot-shiro</description>  
<properties>  
    <java.version>8</java.version>  
</properties>  
<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter</artifactId>  
        <scope>compile</scope>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-thymeleaf</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-redis</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-jdbc</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>com.mysql</groupId>  
        <artifactId>mysql-connector-j</artifactId>  
        <scope>runtime</scope>  
    </dependency>  
    <dependency>  
        <groupId>org.mybatis.spring.boot</groupId>  
        <artifactId>mybatis-spring-boot-starter</artifactId>  
        <version>2.1.3</version>  
    </dependency>  
    <dependency>  
        <groupId>org.projectlombok</groupId>  
        <artifactId>lombok</artifactId>  
        <optional>true</optional>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-test</artifactId>  
        <scope>test</scope>  
    </dependency>  
    <dependency>  
        <groupId>org.apache.shiro</groupId>  
        <artifactId>shiro-spring-boot-web-starter</artifactId>  
        <version>1.11.0</version>  
    </dependency>  
    <dependency>  
        <groupId>org.apache.commons</groupId>  
        <artifactId>commons-lang3</artifactId>  
        <version>3.9</version>  
    </dependency>  
    <dependency>  
        <groupId>cn.hutool</groupId>  
        <artifactId>hutool-all</artifactId>  
        <version>5.3.8</version>  
    </dependency>  
    <!--集成jwt实现token认证-->  
  <dependency>  
        <groupId>com.auth0</groupId>  
        <artifactId>java-jwt</artifactId>  
        <version>3.11.0</version>  
    </dependency>  
    <dependency>  
        <groupId>io.jsonwebtoken</groupId>  
        <artifactId>jjwt</artifactId>  
        <version>0.9.1</version>  
    </dependency>  
</dependencies>  
  
<build>  
    <plugins>  
        <plugin>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-maven-plugin</artifactId>  
            <configuration>  
                <excludes>  
                    <exclude>  
                        <groupId>org.projectlombok</groupId>  
                        <artifactId>lombok</artifactId>  
                    </exclude>  
                </excludes>  
            </configuration>  
        </plugin>  
    </plugins>  
</build>

自定义Realm

在Shiro中,Realm是用于进行身份验证和授权的组件。它表示一个安全数据源,用于获取存储在系统中的用户、角色和权限信息。Realm从数据源中获取用户凭证并进行身份验证,然后根据用户的身份和权限提供授权。

  
import com.example.springbootshiro.entity.User;  
import com.example.springbootshiro.util.BcryptUtil;  
import org.apache.commons.lang3.StringUtils;  
import org.apache.shiro.SecurityUtils;  
import org.apache.shiro.authc.*;  
import org.apache.shiro.authc.credential.CredentialsMatcher;  
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;  
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.subject.Subject;  
import org.springframework.stereotype.Component;  
  
import java.util.ArrayList;  
import java.util.List;  
  
@Component  
public class UserRealm extends AuthorizingRealm {  
  
  
    /**  
 * 授权  
  *  
 * @param principalCollection  
  * @return  
  */  
  @Override  
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {  
        //获取当前用户身份信息  
  Subject subject = SecurityUtils.getSubject();  
        User user = (User) subject.getPrincipal();  
  
        //String principal = principalCollection.getPrimaryPrincipal().toString();  
  
 //调用接口方法获取用户的角色信息  
  List<String> roles = getUserRoleInfo(user.getUserName());  
        //调用接口方法获取用户角色的权限信息  
  List<String> permissions = getUserPermissionInfo(roles);  
  
        //创建对象,存储当前登录的用户的权限和角色  
  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
        //存储角色  
  info.addRoles(roles);  
        //存储权限信息  
  info.addStringPermissions(permissions);  
        //返回  
  return info;  
    }  
  
  
  
    /**  
 * 认证  
  *  
 * @param authenticationToken  
  * @return  
  * @throws AuthenticationException  
 */  @Override  
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {  
        //authenticationToken 包含用户名和密码  
  UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;  
        User user = findByName(token.getUsername());  
        if (user != null) {  
            //校验用户传递的密码和数据库的密码是否一致  
  return new SimpleAuthenticationInfo(user,user.getPassWord(),getName());  
        }  
  
        return null;  
    }  
  
    /**  
 * 设置自定义认证加密方式  
  *  
 * @param credentialsMatcher 默认加密方式  
  */  
  @Override  
  public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {  
        //自定义认证加密方式  
  CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher();  
        // 设置自定义认证加密方式  
  super.setCredentialsMatcher(customCredentialsMatcher);  
    }  
  
    /**  
 * 继承SimpleCredentialsMatcher类,用于重写验证密码方法,验证完成后返回true   false来告诉shiro密码是否正确  
  * 自定义加密方法  
  */  
  public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {  
  
        @Override  
  public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {  
            //获得前台传过来的密码  
  String originalPassword=new String((char[]) token.getCredentials());  
            //这是数据库里查出来的密码  
  String sqlOriginalPassword=(String)info.getCredentials();  
            //将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false  
  return BcryptUtil.match(originalPassword,sqlOriginalPassword);  
        }  
    }  
  
  
    /**  
 * 判断用户是否存在(实际使用数据库查询来进行判断)  
  *  
 * @param userName  
  * @return  
  */  
  private User findByName(String userName) {  
        if (StringUtils.isNotBlank(userName) && StringUtils.equals(userName, "admin")) {  
            return new User(userName, "$2a$10$6zcuv5O0Ugucwb8I4MxQVeNIwvK8b9Pjfaffhl70Cd/buNBqb0mPq", 18, 123456789);  
        }  
        return null;  
    }  
  
    /**  
 * 获取用户的角色信息  
  * @param principal  
  * @return  
  */  
  private List<String> getUserRoleInfo(String principal){  
        List<String> list = new ArrayList<>(1);  
        list.add("admin");  
        return list;  
    }  
  
    /**  
 * 获取用户的权限信息  
  * @param roles  
  * @return  
  */  
  private List<String> getUserPermissionInfo(List<String> roles) {  
        List<String> list = new ArrayList<>(1);  
        list.add("user:create");  
        return list;  
    }  
}
import cn.hutool.crypto.digest.BCrypt;  
  
public class BcryptUtil {  
    /**  
 * 对明文密码进行加密,并返回加密后的密码  
  *  
 * @param password  
  * @return  
  */  
  public static String encode(String password) {  
        return BCrypt.hashpw(password, BCrypt.gensalt());  
    }  
  
    /**  
 * 将明文密码跟加密后的密码进行匹配,如果一致返回true,否则返回false  
 * * @param password  
  * @param encodePassword  
  * @return  
  */  
  public static boolean match(String password, String encodePassword) {  
        return BCrypt.checkpw(password, encodePassword);  
    }  

}
import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  
  
import java.io.Serializable;  
  
@Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class User implements Serializable {  
    private String userName;  
    private String passWord;  
    private Integer age;  
    private Integer phone;  
}

配置shiro

  
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
import java.util.LinkedHashMap;  
import java.util.Map;  
  
@Configuration  
public class ShiroConfig {  
  
    @Autowired  
  private UserRealm userRealm;  
  
    /**  
 * 配置 Shiro 的过滤器链  
  * @param securityManager  
  * @return  
  */  
  @Bean  
  public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){  
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();  
        factoryBean.setSecurityManager(securityManager);  
        //权限设置  
  
  /**  
 * 认证过滤器  
  * anon 无需认证  
  * authc 必须认证  
  * authcBasic 需要通过HTTPBasic认证  
  * user 不一定通过认证,只要曾经被shiro记录即可  
  *  
 */ /** * 授权过滤器  
  * perms 必须拥有某个权限才能访问  
  * role 必须拥有某个角色才能访问  
  * port 请求的端口必须指定值才能访问  
  * rest 请求必须基于RESTFUL  
 * ssl 必须是安全的URl请求,协议HTTPS  
 * */ // 设置登录页面  
  factoryBean.setLoginUrl("/login");  
  
        // 设置未授权页面  
  factoryBean.setUnauthorizedUrl("/unauthorized");  
  
        // 定义过滤器链  
  Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();  
  
        // 公开的资源,无需身份验证  
  filterChainDefinitionMap.put("/css/**", "anon");  
        filterChainDefinitionMap.put("/js/**", "anon");  
        filterChainDefinitionMap.put("/public/**", "anon");  
        filterChainDefinitionMap.put("/login", "anon");  
        filterChainDefinitionMap.put("/index", "anon");  
  
        // 需要身份验证的资源  
  filterChainDefinitionMap.put("/private/**", "authc");  
  
        // 需要特定角色才能访问的资源  
  filterChainDefinitionMap.put("/admin/**", "roles[admin]");  
  
        // 需要特定权限才能访问的资源  
  filterChainDefinitionMap.put("/user/**", "perms[user:create]");  
  
        // 其他资源都需要身份验证  
  filterChainDefinitionMap.put("/**", "authc");  
  
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
        return factoryBean;  
    }  
  
    /**  
 * 配置SecurityManager  
 * @return  
  */  
  @Bean  
  public DefaultWebSecurityManager securityManager(){  
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();  
        manager.setRealm(userRealm);  
        return manager;  
    }  
}

配置Controller

import com.example.springbootshiro.entity.User;  
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.subject.Subject;  
import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  
  
@Controller  
public class UserController {  
  
    @GetMapping("{url}")  
    public String redirect(@PathVariable("url")String url){  
        return url;  
    }  
  
    @ResponseBody  
 @GetMapping("unauthorized")  
    public String unauthorized(){  
        return "未授权,无法访问!";  
    }  
  
    @GetMapping("logout")  
    public String logout(){  
        Subject subject = SecurityUtils.getSubject();  
        subject.logout();  
        return "login";  
    }  
  
    @PostMapping("login")  
    public String login(String username, String password, Model model){  
        //包含用户信息  
  Subject subject = SecurityUtils.getSubject();  
        //组装UsernamePasswordToken  
  UsernamePasswordToken token = new UsernamePasswordToken(username,password);  
        //将认证交给shiro处理 -> doGetAuthenticationInfo(Realm)  try {  
            subject.login(token);  
            User user = (User) subject.getPrincipal();  
            subject.getSession().setAttribute("user",user);  
            return "index";  
        } catch (UnknownAccountException e) {  
            e.printStackTrace();  
            model.addAttribute("msg","用户名不存在!");  
            return "login";  
        } catch (IncorrectCredentialsException e) {  
            e.printStackTrace();  
            model.addAttribute("msg","密码错误!");  
            return "login";  
        }  
    }  
}

Q.E.D.