Apache Shiro是一个功能强大的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、加密(Cryptography)、会话管理(Session Management)、与Web集成、缓存(Caching)等核心安全功能。
Shiro认证的核心概念
- Subject:代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫、机器人等。它是一个抽象概念,所有Subject都绑定到SecurityManager。
- SecurityManager:安全管理器,Shiro的核心组件,所有与安全有关的操作都会与SecurityManager交互。它管理着所有Subject,并负责进行认证和授权、会话及缓存的管理。
- Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限)。SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。可以把Realm看成DataSource,即安全数据源。
- Authenticator:认证器,负责主体认证,是一个扩展点。如果用户觉得Shiro默认的不好,可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
Shiro认证的流程:
1. 添加依赖项
<!-- shiro认证--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>2.0.1</version></dependency><!-- 导入shiro标签 --><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.1.0</version>
2. 收集用户身份/凭证:如用户名/密码。 配置shiro.ini文件
3.认证测试
编写测试类
@Testpublic void testShiro(){// 1.创建 Realm(安全数据源)IniRealm realm = new IniRealm("classpath:shiro.ini");// 2.配置DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);// 创建注入的 Realm(安全数据源)securityManager.setRealm(realm);SecurityUtils.setSecurityManager(securityManager);// 3.操作 Subject , 进行认证Subject subject = SecurityUtils.getSubject();// 封装一个令牌UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token); // 登录,即认证}catch (AuthenticationException e){System.out.println("认证异常:");e.printStackTrace();}System.out.println("是否通过认证: " + subject.isAuthenticated());// 认证通过后 进行权限认证System.out.println("是否为管理员角色:" + subject.hasRole("管理员")); // 是否为某角色System.out.println("是否能操作用户查看功能:" + subject.isPermitted("user:view")); // 判断是否拥有某权subject.checkPermission("user:view");System.out.println("身份信息: " + subject.getPrincipal());/*** 18:43:30.420 [main] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager -- Enabling session validation scheduler...* 是否通过认证: true* 是否为管理员角色:true* 是否能操作用户查看功能:true* 身份信息: admin*/}
可以看到我们的一个运行结果 身份认证成功
那如果我们修改密码呢:
可以看到如果密码和我们的不一样就会 认证异常 !!
- 首先通过指定一个ini配置文件来创建一个IniRealm对象;
- 接着实例化一个 DefaultSecurityManager,并注入IniRealm 对象;
- 再将 DefaultSecurityManager 绑定到 SecurityUtils,这是一个全局设置,设置一次即可通过 SecurityUtils 得到 Subject;
- 然后获取身份验证的 Token,如用户名/密码,此处使用UsernamePasswordToken;调用 subject.login 方法进行登录,其会自动委托给 SecurityManager.login 方法进行登录:
- 如果身份验证失败请捕获 AuthenticationException或其子类,常见的如:DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException(错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
- 如果身份认证成功,后续可以使用subject.isAuthenticated()判断是否认证通过subject.getPrincipal()获得身份信息等。
那么 Shiro认证的流程到低是什么呢?
流程如下:
- 首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager, 调用之前必须通过 SecurityUtils.setSecurityManager()设置Security Manager;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证:
- Authenticator才是真正的身份验证者,ShiroAPI中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行Realm 身份验证;
- Authenticator会把相应的token传入Realm,从Realm获取身份信息,如果没有返验证;回抛出异常表示身份验证失败了。此处可以配置多个ealm,将按照相应的顺序及策略进行访问。
Shiro 的使用方法:
ShiroConfig :
package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() { // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() { // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() { // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon"); // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc"); // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}
indexContriller:
package com.ty.springbootshiro.controller;import com.alibaba.druid.sql.visitor.functions.Right;
import com.ty.springbootshiro.entity.Role;
import com.ty.springbootshiro.entity.User;
import com.ty.springbootshiro.service.RoleService;
import com.ty.springbootshiro.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.RequestMapping;import java.util.List;/*** IndexConfig** @aurhor Administrator whs* @since 2024/9/13*/
@Controller
public class IndexController {@Resourceprivate UserService userService;@Resourceprivate RoleService roleService;/*** 去登录页*/@GetMapping("/login")public String toLogin() {return "login";}@RequestMapping("/main")public String main() {return "main";}@RequestMapping("/403")public String unauthorized() {return "403";}@RequestMapping("/login")public String login(String usrName, String usrPassword, Model model, HttpSession session) {
// User loginUser = userService.login(usrName, usrPassword);
// if (loginUser != null) {
// session.setAttribute("loginUser", loginUser);
// return "redirect:/main";
// }else {
// model.addAttribute("meg","账号或密码错误");
// return "login";
// }try {UsernamePasswordToken token = new UsernamePasswordToken(usrName, usrPassword);Subject subject = SecurityUtils.getSubject();subject.login(token); // 认证登录User user = (User) subject.getPrincipal();System.out.println("user ------ > " + user);session.setAttribute("loginUser", user);//获取权限
// Role role = user.getRole();
// List<Right> rights = roleService.findRightsByRole(role);
// role.getRights().addAll(rights);
// model.addAttribute("rights", rights);session.setAttribute("loginUser", user);return "redirect:/main";}catch (UnknownAccountException | IncorrectCredentialsException e) {model.addAttribute("msg", "用户名或密码错误,登录失败!");return "login";}catch (LockedAccountException e) {model.addAttribute("msg", "用户被禁用,登录失败!");return "login";}catch (AuthenticationException e) {model.addAttribute("msg", "认证异常,登录失败!");return "login";}}@RequestMapping("/logout")public String logout(HttpSession session) {session.removeAttribute("loginUser");SecurityUtils.getSubject().logout(); // shiro 注销return "redirect:/main"; //重定向 保证删除Cookie}
}
ShiroConfig:
package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() { // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() { // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() { // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon"); // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc"); // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}
shiro 控制的三种状态:
1.
2.
3.
以上便是简单的 shiro 认证 下一章我会讲解什么是shiro 授权