Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它为开发人员提供了一个直观而全面的身份验证、授权、加密和会话管理解决方案。
shiro框架的基本使用和介绍详情可见下面这篇文章供大家了解和参考:
1.创建一个SpringBoot项目并且添加如下依赖
本文使用的SpringBoot版本如下是2.7.0
引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro 注解会用到 aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
2.编写自定义的Realm继承AuthorizingRealm重写两个方法
1.doGetAuthorizationInfo方法:doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用。
2.doGetAuthenticationInfo方法:doGetAuthenticationInfo是认证的方法,用户首次登录的时候会进入这里进行密码校验。
public class TryShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//AuthorizationInfo 接口的简单 POJO 实现,将角色和权限存储为内部属性,获取角色的信息
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//这个可以用来获取在登录的时候提交的其他额外的参数信息
// HttpServletRequest request = (HttpServletRequest) ((WebSubject)(SecurityUtils
// .getSubject())).getServletRequest();
String username = (String) principals.getPrimaryPrincipal();
// 受理权限
// 角色
Set<String> roles = new HashSet<>();
//查找并添加角色
Role role = roleService.getRoleByUserName(username);
System.out.println(role.getRoleName());
roles.add(role.getRoleName());
//设置角色名
authorizationInfo.setRoles(roles);
// 权限
Set<String> permissions = new HashSet<>();
List<Permission> queryPermissions = permissionService.getPermissionsByRoleId(role.getId());
for (Permission permission : queryPermissions) {
permissions.add(permission.getPermissionName());
}
//设置权限
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* doGetAuthenticationInfo是认证的方法
* 用户首次登录的时候会进入这里
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
String loginName = (String) authenticationToken.getPrincipal();
//获取用户的密码
User user = userService.getOne(new QueryWrapper<User>().eq("username", loginName));
if (user == null) {
//不存在账号
throw new UnknownAccountException();
}
String password = new String((char[]) authenticationToken.getCredentials());
String inpass = (new Md5Hash(password, user.getUsername())).toString();
//加密后的密码与数据库中的密码进行比较
if (!user.getPassword().equals(inpass)) {
throw new IncorrectCredentialsException();
}
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, user.getPassword(),
ByteSource.Util.bytes(loginName), getName());
return authenticationInfo;
}
}
3.编写ShiroConfiguration
编写shiro的配置并注入。
ShiroFilterFactoryBean:定义主 Shiro 过滤器。
HashedCredentialsMatcher:HashedCredentialMatcher 在与数据存储中的 AuthenticationInfo 中的凭据进行比较之前,支持对提供的 AuthenticationToken 凭据进行散列处理。可以理解为加密方式。
SecurityManager:为单个应用程序中的所有主题(也称为用户)执行所有安全操作。也可以理解为扩展了 Authenticator、Authenticator 和 SessionManager 接口,从而将这些行为整合到一个单一的参考点中。对于大多数 Shiro 用法,这简化了配置,并且往往比分别引用 Authenticator、Authorizer 和 SessionManager 实例更方便;相反,只需要与单个 SecurityManager 实例进行交互。
@Configuration
public class ShiroConfiguration {
/**
* Sl4j日志输出
*/
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
/**
* shiro的web过滤器Factory 命名为:shiroFilter
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 需要权限的请求,如果没有登录则会跳转到这里设置的url
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 设置登录成功跳转url,一般在登录成功后自己代码设置跳转url,此处基本没用
shiroFilterFactoryBean.setSuccessUrl("/main.html");
// 设置无权限跳转界面,此处一般不生效,一般自定义异常
shiroFilterFactoryBean.setUnauthorizedUrl("/error.html");
LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>();
shiroFilterFactoryBean.setFilters(filterMap);
/*
* 定义shiro过滤器链 Map结构
* Map中key(xml中是指value的值)的第一个‘/’代表的路径是相对于HttpServletRequest.getContextPath()的值来的
* anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
* authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.
* FormAuthenticationFilter
*/
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/*
* 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
* authc:所有url都必须认证通过才可以访问;
* anon: 所有url都都可以匿名访问
*/
filterChainDefinitionMap.put("/login.html", "authc");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数
hashedCredentialsMatcher.setHashIterations(1);
// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* Shiro Realm 继承自AuthorizingRealm的自定义Realm
* 即指定Shiro验证用户登录的类为自定义的
* @return
*/
@Bean
public TryShiroRealm TryShiroRealm() {
TryShiroRealm tryShiroRealm = new TryShiroRealm();
tryShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return tryShiroRealm;
}
/**
* 权限管理
* @return
*/
@Bean
public SecurityManager securityManager() {
logger.info("===========================shiro=========================");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(TryShiroRealm());
//
return securityManager;
}
/**
* 开启shiro注解(如@RequiresRoles,@RequiresPermission)
* ,需借助SpringAOP扫描使用Shiro注解的类,并再必要时进行安全逻辑验证
* 通过注入 Advisor 来增强一些类的和方法
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
4.自定义异常类
/**
* 自定义异常类
* @ControllerAdvice 本质上是一个Component,因此也会被当成组建扫描
* 是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来
* 具体你想做更细致的拦截筛选和拦截之后的处理,
* 你自己通过@ExceptionHandler、@InitBinder 或 @ModelAttribute这三个注解以及被其注解的方法来自定义
*/
@ControllerAdvice
public class ShiroException {
/**
* 接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常
* 拦截异常后进行处理
* @return
*/
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public String name() {
return "没有权限!";
}
}
5.控制层代码:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@PostMapping("/login")
public String name(String username, String password) {
String result = "已经登录";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (!currentUser.isAuthenticated()) {
try {
// 会触发doGetAuthenticationInfo方法
currentUser.login(token);
result = "登录成功";
} catch (UnknownAccountException e) {
result = "用户名错误";
} catch (IncorrectCredentialsException e) {
result = "密码错误";
}
}
return result;
}
@GetMapping("/logout")
public void logout() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
@RequiresPermissions("role:update")
@GetMapping("/role")
public String name() {
return "hello";
}
@RequiresPermissions("user:select")
@GetMapping("/role2")
public String permission() {
return "hello role2";
}
}
6.源代码以及数据库文件拉取位置: