项目依赖
<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.