Shiro 认证(Authentication)
Shiro 简介
Shiro是Apache旗下的一个开源Java安全(权限)框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证、权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。以下是关于Shiro的详细介绍:
一、Shiro的主要功能
- 身份认证:验证用户是否拥有相应的身份,例如通过账号、密码进行登录验证。
- 权限授权:验证某个已认证的用户是否拥有访问某个资源或执行某个操作的权限。
- 加密:提供加密组件用于数据的加密和解密,保护数据的安全性,如密码加密存储到数据库。
- 会话管理:管理用户登录后的会话信息,包括会话的创建、维护和销毁等。
- Web集成:Shiro可以非常容易地集成到Web环境中,提供Web级别的安全控制。
- 缓存:提供缓存管理器,用于缓存用户、角色、权限等安全数据,提高访问性能。
二、Shiro的核心组件
- Subject:主体,代表了当前用户或与应用交互的任何东西(如网络爬虫、机器人等)。它是应用代码直接交互的对象,所有的Subject都绑定到SecurityManager。
- SecurityManager:安全管理器,Shiro的核心组件。它管理着所有的Subject,并负责进行认证、授权、会话和缓存等管理。所有与安全有关的操作都会与SecurityManager交互。
- Realm:领域对象,是Shiro和应用程序安全数据之间的桥梁。它负责从数据层获取安全数据(如用户、角色、权限等),以供SecurityManager进行认证和授权。
此外,Shiro还包括以下组件:
- Authenticator:认证器,负责主体的认证操作。
- Authorizer:授权器,或访问控制器,用于决定主体是否有权限进行相应的操作。
- SessionManager:会话管理器,负责创建并管理用户Session的生命周期。
- SessionDAO:会话数据访问对象,用于会话的CRUD(创建、读取、更新、删除)操作。
- CacheManager:缓存控制器,用于管理用户、角色、权限等安全数据的缓存。
- Cryptography:密码模块,提供加密方式的设计及管理。
三、Shiro的运行原理
- 应用程序代码在需要进行权限控制时,会调用Subject的API。
- Subject将请求委托给SecurityManager进行处理。
- SecurityManager根据请求的类型(认证或授权),调用相应的组件(Authenticator或Authorizer)进行处理。
- Authenticator或Authorizer会从Realm中获取所需的安全数据,并进行相应的认证或授权判断。
- 最后,将判断结果返回给应用程序代码,以决定是否允许用户访问资源或执行操作。
四、Shiro的权限控制方式
Shiro提供了多种权限控制方式,以满足不同应用场景的需求:
- URL级别权限控制:通过配置过滤器,对不同的URL地址进行访问控制。
- 方法注解权限控制:在方法上使用注解来指定访问权限,以实现细粒度的权限控制。
- 代码级别权限控制:在代码中编写逻辑来判断用户是否有权限执行某个操作。
- 页面标签权限控制:在Web页面中,通过标签来控制用户对不同页面元素的访问权限。
五、Shiro与Spring Security的比较
Shiro和Spring Security都是Java领域常用的安全框架。相比Spring Security,Shiro在保持强大功能的同时,具有更高的使用简单性和灵活性。Spring Security必须与Spring环境一起使用,而Shiro则可以独立运行,不依赖于任何框架。此外,Shiro还提供了更易于理解和使用的API接口。
认证(Authentication)
认证(Authentication)是验证用户身份的过程,即证明一个用户实际上是不是他们所说的他们是谁。以下是关于认证的详细介绍:
一、认证的基本概念
在认证过程中,用户需要提交实体信息(如用户名)和凭据信息(如密码)以检验用户是否合法。这是一个关键的安全机制,用于确保只有合法的用户才能访问系统资源或执行特定操作。
二、认证的流程
以Shiro框架为例,认证流程通常包括以下几个步骤:
- 用户提交身份信息:用户在应用程序的登录页面输入用户名和密码,并提交身份信息。
- Subject封装身份信息:应用程序接收到用户提交的身份信息后,将其封装为Subject对象。Subject代表了当前操作用户,可以是任何与应用交互的“用户”,如人、网络爬虫、机器人等。
- SecurityManager开始认证:SecurityManager是Shiro的核心组件,负责协调和管理所有的安全操作。它接收到Subject提交的身份信息后,开始进行身份验证。
- SecurityManager调用Authenticator进行身份验证:SecurityManager会调用配置好的Authenticator进行身份验证。Authenticator是Shiro框架中用于执行认证操作的组件。
- Authenticator获取身份信息:Authenticator使用Realm(可能是单个Realm或多个Realm的组合)从数据源中获取用户的身份信息。Realm是连接Shiro和安全数据源的桥梁,它根据配置的方式(如数据库、LDAP等)获取用户的身份信息。
- Realm获取用户身份信息:Realm根据配置的数据源和查询条件,从数据源中检索用户的身份信息。
- Authenticator进行身份匹配:Authenticator将用户提交的身份信息和Realm获取到的用户身份信息进行匹配,以确定用户是否合法。
- 认证结果返回给SecurityManager:Authenticator将认证结果(通过或失败)返回给SecurityManager。
- SecurityManager处理认证结果:SecurityManager根据认证结果,如果认证成功,则将用户标记为已认证状态,并将用户的身份信息存储在Subject中供以后使用。如果认证失败,则抛出相应的异常。
- 认证结果返回给应用程序:SecurityManager将认证结果返回给应用程序,应用程序可以根据认证结果决定如何处理(如允许用户访问系统资源或显示错误消息)。
三、认证的重要性
认证机制是保护系统安全性的重要手段之一。通过验证用户的身份,系统可以确保只有合法的用户才能访问敏感数据或执行关键操作。这有助于防止未经授权的访问和潜在的安全威胁。
四、常见的认证方式
- 用户名/密码认证:最常见的认证方式之一,用户通过输入用户名和密码来验证身份。
- 双因素认证:除了用户名和密码外,还需要用户提供额外的验证信息(如手机验证码、指纹识别等)来增强安全性。
- 单点登录(SSO):用户只需在一个地方登录一次,即可访问多个相互信任的应用系统。
- OAuth和OpenID:用于在第三方应用程序中授权用户访问其资源,而无需将用户的登录信息泄露给第三方。
五、快速上手
1.添加依赖项
此处直接添加Shiro和spring集成的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>2.0.1</version>
</dependency>
2.配置shiro.ini
在resources目录下新建shiro.ini文件配置用户认证数据
[users]
#用户账户和密码
#配置规则:用户账户=密码
admin=123456
czkt=111111
3.认证测试
创建测试类
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.jupiter.api.Test;
public class ShiroTester {
@Test
public void testShiro(){
IniRealm realm=new IniRealm("classpath:shiro.ini");
DefaultSecurityManager securityManager=new DefaultSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
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.getPrincipal());
}
}
SpringBoot + Shiro认证
实现动态认证,除了对原CRM项目代码进行细微的调整之外,Shiro部分只要做两件事即可:1、自定义Realm;2、配置Shiro相关对象
基础代码调整
UserRepository新增findUserByUsrName方法:
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
public List<User>findUsersByNameAndUsrPassword(String usrName,String usrPassword);//等录
//根据用户名查询用户对象
public User findUserByUsrName(String usrName);
}
UserService接口新增getUserByUsrName方法
//根据用户名查询用户
User getUserByUsrName(String username);
UserServiceImpl实现新增getUserByUsrName方法
@Override
public User getUserByUsrName(String username) {
return null;
}
自定义Realm
自定义Realm需要继承AuthorizingRealm类,该类封了很多方法,且继承自Realm类。继承AuthorizingRealm类后,我们需要重写以下两个方法:
doGetAuthenticationlnfo() 方法:获取身份信息
doGetAuthorizationlnfo() 方法:获取权限信息(角色和权限)
创建自定义Realm对象MyShiroRealm:
import com.pdqn.pojo.User;
import com.pdqn.service.UserService;
import jakarta.annotation.Resource;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyShiroRealm extends AuthorizingRealm{
@Resource
private UserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("调用MyShiroRealm.doGetAuthenticationInfo 获取身份信息!");
UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
String username=token.getUsername();
User user=userService.getUserByUsrName(username);
if(user!=null){
throw new UnknownAccountException();
}
if (user.getUsrFlag()==null || user.getUsrFlag().intValue()==0){
throw new LockedAccountException();
}
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPassword(),getName());
return info;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("调用MyShiroRealm.doGetAuthorizationInfo 获取授权信息!");
User user=(User) principalCollection.getPrimaryPrincipal();
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo();
return (AuthorizationInfo) info;
}
}
配置Shiro相关对象
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm shiroRealm=new MyShiroRealm();
return shiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultSecurityManager securityManager=new DefaultSecurityManager();
securityManager.setRealm(myShiroRealm());
// securityManager.setSessionManager(securityManager());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
}
IndexController重写登录方法
import com.pdqn.pojo.User;
import com.pdqn.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;
@Controller
public class IndexController {
@Resource
private UserService userService;
@RequestMapping(value = "/index")
public String index(){
return "login";
}
@RequestMapping(value = "/main")
public String main(){
return "main";
}
@RequestMapping(value = "/login")
public String login(String usrName, String usrPassword, Model model, HttpSession session){
try {
UsernamePasswordToken token=new UsernamePasswordToken(usrName,usrPassword);
Subject subject= SecurityUtils.getSubject();
subject.login(token);
User user=(User) subject.getPrincipal();
session.setAttribute("user",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";
}
}
@GetMapping(value = "/logout")
public String logout(HttpSession session){
session.removeAttribute("loginUser");
SecurityUtils.getSubject().logout();
return "redirect:/main";
}
}