Shiro认证 -- (Authentication)
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.认证测试
编写测试类
@Test
public 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 {
@Resource
private RoleService roleService;
/**
* 开启Shiro注解
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启aop注解支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() { // thymeleaf 页面上使用 shiro 标签
return new ShiroDialect();
}
@Bean
public MyShiroRealm myShiroRealm() { // 自定义Realm
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public SecurityManager securityManager() { // 安全管理器 SecurityManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//注入bean
securityManager.setRealm(myShiroRealm());
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证
ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
// 注入securityManager
shiroFilterFactory.setSecurityManager(securityManager);
// 权限验证 : 使用 Filter 控制资源(URL)的访问
shiroFilterFactory.setLoginUrl("/index");
shiroFilterFactory.setSuccessUrl("/main");
shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403
Map<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
// 静态授权: 包括全部需要特定权限才能访问的资源URl
filterChainDefinitionMap.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 {
@Resource
private UserService userService;
@Resource
private 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 {
@Resource
private RoleService roleService;
/**
* 开启Shiro注解
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启aop注解支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() { // thymeleaf 页面上使用 shiro 标签
return new ShiroDialect();
}
@Bean
public MyShiroRealm myShiroRealm() { // 自定义Realm
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public SecurityManager securityManager() { // 安全管理器 SecurityManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//注入bean
securityManager.setRealm(myShiroRealm());
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证
ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
// 注入securityManager
shiroFilterFactory.setSecurityManager(securityManager);
// 权限验证 : 使用 Filter 控制资源(URL)的访问
shiroFilterFactory.setLoginUrl("/index");
shiroFilterFactory.setSuccessUrl("/main");
shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403
Map<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
// 静态授权: 包括全部需要特定权限才能访问的资源URl
filterChainDefinitionMap.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 授权