Shiro之认证
系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
Shiro之认证
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
当涉及到应用程序的安全性时,身份验证和授权是至关重要的环节。Shiro 框架以其简单而强大的特性,成为了处理这两个关键任务的流行选择。在这篇博客中,我们将深入探讨 Shiro 框架的认证功能。
通过使用 Shiro,你将能够轻松地管理用户的身份验证过程,确保只有经过授权的用户能够访问受保护的资源。我们将了解 Shiro 的核心概念,如 Subject、Realm 和 Credentials,以及它们在认证过程中的作用。
博客将详细介绍 Shiro 支持的多种认证方式,包括用户名和密码、证书、RememberMe 等。你将学会如何配置和实现这些认证方式,以满足你的应用程序的特定需求。
我们还将探讨 Shiro 的一些高级特性,如权限管理、会话管理和加密等。这些特性将帮助你进一步加强应用程序的安全性,并提供更细粒度的访问控制。
无论你是刚刚开始使用 Shiro,还是已经有一定经验的开发者,这篇博客都将为你提供宝贵的资源和深入的理解。让我们一起探索 Shiro 的认证功能,为你的应用程序构建一个安全可靠的基础。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是Shiro
Shiro是apache旗下的一个开源安全框架,它可以帮助我们完成身份认证,授权、加密、会话管理等功能。Shiro 的主要目标是让开发者能够轻松地保护他们的应用程序,使其免受未经授权的访问。它有如下特点:
- 不依赖任何的框架或者容器捆绑,可以独立运行
- 内置会话管理,适用于Web以及非Web的环境
- 支持缓存,以提升应用程序的性能
- 易于理解的API
- 简单的身份认证,支持多种数据源
- 简单的认证与授权
二、Shiro的核心功能
Shiro 是一个强大而灵活的 Java 安全框架,其核心功能包括身份验证、授权、会话管理和加密等。以下是 Shiro 的一些核心功能的详细介绍:
- 身份验证(Authentication):Shiro 支持多种身份验证方式,如用户名和密码、证书、RememberMe 等。它可以与各种数据源集成,如关系型数据库、LDAP 等,以验证用户的身份。
- 授权(Authorization):一旦用户通过身份验证,Shiro 可以根据用户的角色和权限进行授权。它支持基于角色的访问控制(RBAC)和基于权限的访问控制(PBAC)等授权模型。
- 会话管理(Session Management):Shiro 提供了会话管理功能,包括创建、维护和销毁会话。它可以与 Web 容器集成,以实现会话的超时和续命。
- 加密(Encryption):Shiro 支持密码加密、数据加密和签名等加密功能,以确保敏感数据的安全性。
- 缓存(Caching):Shiro 内置了缓存机制,可以提高性能并减少对数据源的访问。
- 领域对象(Realm):Shiro 鼓励使用领域对象来表示安全主体(如用户、角色等)和权限,使代码更加清晰和易于维护。
- 灵活性(Flexibility):Shiro 具有高度的灵活性,可以轻松地与其他框架(如 Spring、Hibernate 等)集成。
- 可配置性(Configurability):Shiro 提供了灵活的配置选项,可以通过配置文件或注解来定义安全策略。
三、Shiro的核心组件
Shiro 框架有许多核心组件,它们协同工作以提供全面的安全功能。以下是一些 Shiro 的核心组件:
- Subject:Subject 表示当前执行操作的用户或主体。它持有用户的身份信息和权限信息,并提供了执行安全操作的接口。
- SecurityManager:SecurityManager 是 Shiro 框架的核心,它负责管理所有的安全操作。它是整个 Shiro 安全体系的入口点,应用程序通过调用 SecurityManager 来进行身份验证、授权等操作。
- Realm:Realm 是 Shiro 与数据源进行交互的桥梁。它用于存储和检索用户、角色和权限等信息。Shiro 支持多种类型的 Realm,如基于内存的 Realm、基于数据库的 Realm 等。
- Session:Session 用于管理用户与应用程序之间的会话。它跟踪用户的登录状态,并在需要时提供对用户信息的访问。
- Authenticator:Authenticator 负责执行用户的身份验证。它可以通过用户名和密码、证书等方式验证用户的身份。
- Authorizer:Authorizer 负责执行授权操作,即确定用户是否具有访问特定资源或执行特定操作的权限。
- CachingManager:CachingManager 用于管理 Shiro 的缓存。它可以缓存用户、角色和权限等信息,以提高性能。
- LogoutFilter:LogoutFilter 用于处理用户的登出操作。它确保在用户登出后清除相关的会话信息。
四、Shiro之认证
项目搭建
1.准备名为myshiro的mysql数据库
2.创建SpringBoot项目,加入相关依赖
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!-- shiro和spring整合包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Junit -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
3.编写配置文件application.yml
server:
port: 80
#日志格式
logging:
pattern:
console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
# 数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///myshiro?serverTimezone=UTC
username: root
password: root
4.在template文件夹编写项目主页面main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
<h1>主页面</h1>
</body>
</html>
5.编写页面跳转控制器
@Controller
public class PageController {
@RequestMapping("/{page}")
public String showPage(@PathVariable String page) {
return page;
}
// 忽略favicon.ico的获取
@GetMapping("favicon.ico")
@ResponseBody
public void noFavicon() {}
}
自定义Realm
Realm 可以与数据源(如关系型数据库、LDAP 服务器等)进行集成,以获取和验证用户的认证和授权信息。Shiro 提供了多种内置的 Realm 实现,如 IniRealm、JdbcRealm、LdapRealm 等,同时也支持自定义 Realm 以满足特定的需求。
在 Shiro 的安全体系中,Subject 是代表当前用户的对象,它与 Realm 进行交互以进行认证和授权操作。当用户尝试进行身份验证或请求授权时,Subject 会将请求转发给配置的 Realm,Realm 则根据存储的用户信息进行验证和授权决策。
1.准备数据表
CREATE TABLE `users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` VALUES (1, 'zhangsan', '123');
2.编写实体类
@Data
public class Users {
private Integer uid;
private String username;
private String password;
}
3.编写mapper接口
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
}
4.在启动类加载mapper接口
@SpringBootApplication
@MapperScan("com.zhangsan.myshiro1.mapper")
public class Myshiro1Application {
public static void main(String[] args) {
SpringApplication.run(Myshiro1Application.class, args);
}
}
5.编写自定义Realm类
public class MyRealm extends AuthorizingRealm {
@Autowired
private UsersMapper usersMapper;
// 自定义认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.获取用户输入的用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
// 2.根据用户名查询用户
QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
Users users = usersMapper.selectOne(wrapper);
// 3.将查询到的用户封装为认证信息
if (users == null) {
throw new UnknownAccountException("账户不存在");
}
/**
* 参数1:用户
* 参数2:密码
* 参数3:Realm名
*/
return new SimpleAuthenticationInfo(users,
users.getPassword(),
"myRealm");
}
// 自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
容器管理shiro对象
将自定义Realm放入SecurityManager对象中
@Configuration
public class ShiroConfig {
// 自定义Realm
@Bean
public MyRealm myRealm(){
return new MyRealm();
}
// SecurityManager对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
// 自定义Realm放入SecurityManager中
defaultSecurityManager.setRealm(myRealm);
return defaultSecurityManager;
}
}
2.创建UserService对象
@Service
public class UsersService {
@Autowired
private DefaultWebSecurityManager securityManager;
public void userLogin(String username, String password) throws AuthenticationException {
// 1.将SecurityManager对象设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 2.获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 3.将前端传来的用户名密码封装为Shiro提供的身份对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 4.Shiro认证
subject.login(token);
}
}
3.编写登录控制器方法
@RequestMapping("/user/login2")
public String login2(String username,String password){
try {
usersService.userLogin(username,password);
return "main";
}catch (AuthenticationException e){
return "fail";
}
}
多Realm认证
在实际的开发中,我们的认真逻辑可能不止一种,例如普通用户登录和管理员登录。
1.在数据库创建admin表
CREATE TABLE `admin` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `admin` VALUES (1, 'lisi', '123');
INSERT INTO `admin` VALUES (2, 'wangwu', '456');
2.创建Admin实体类和AdminMapper接口
@Data
public class Admin {
private Integer id;
private String name;
private String password;
}
public interface AdminMapper extends BaseMapper<Admin> {
}
3.MyRealm认证User用户,MyRealm2认证Admin用户
public class MyRealm2 extends AuthorizingRealm {
@Autowired
private AdminMapper adminMapper;
// 自定义认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.获取输入的管理员名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
// 2.根据管理员名查询管理员
QueryWrapper<Admin> wrapper = new QueryWrapper<Admin>().eq("name", username);
Admin admin = adminMapper.selectOne(wrapper);
// 3.将查询到的管理员封装为认证信息
if (admin == null) {
throw new UnknownAccountException("账户不存在");
}
/**
* 参数1:管理员
* 参数2:密码
* 参数3:Realm名
*/
return new SimpleAuthenticationInfo(admin,
admin.getPassword(),
"myRealm2");
}
// 自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
4.在SecurityManager中配置Realm
// Realm
@Bean
public MyRealm getMyRealm() {
return new MyRealm();
}
@Bean
public MyRealm2 getMyRealm2() {
return new MyRealm2();
}
// SecurityManager对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm realm, MyRealm2 realm2){
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
// Realm放入SecurityManager中
List<Realm> realms = new ArrayList();
realms.add(realm);
realms.add(realm2);
defaultSecurityManager.setRealms(realms);
return defaultSecurityManager;
}
多Realm认证策略
多Realm 认证策略是一种用于身份验证和授权的安全策略,它允许在一个系统或应用程序中使用多个身份验证领域(Realm)来验证用户的身份。认证策略主要使用的是 AuthenticationStrategy接口,这个接口有三个实现类:
策略 | 意义 |
---|---|
AtLeastOneSuccessfulStrategy(默认) | 只要有一个Realm验证成功即可,返回所有成功的认证信息 |
FirstSuccessfulStrategy | 只要有一个Realm验证成功即可,只返回第一个成功的认证信息,其他的忽略 |
AllSuccessfulStrategy | 所有Realm验证成功才算成功,如果有一个失败则认证失败 |
// Realm管理者
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//设置认证策略
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
// SecurityManager对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm realm, MyRealm2 realm2){
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
// 设置Realm管理者(需要在设置Realm之前)
defaultSecurityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList();
realms.add(realm);
realms.add(realm2);
defaultSecurityManager.setRealms(realms);
return defaultSecurityManager;
}
异常处理
当Shiro认证失败后,会抛出AuthorizationException异常,该异常有很多子类,不同的子类意味着认证失败的原本不同,我们可以通过捕获异常确定认证失败的原因。
异常 | 原因 |
---|---|
DisabledAccountException | 账户失效 |
ConcurrentAccessException | 竞争次数过多 |
ExcessiveAttemptsException | 尝试次数过多 |
UnknownAccountException | 用户名不正确 |
IncorrectCredentialsException | 凭证(密码)不正确 |
ExpiredCredentialsException | 凭证过期 |
@RequestMapping("/user/login2")
public String login(String username, String password) {
try {
usersService.userLogin(username, password);
return "main";
} catch (DisabledAccountException e) {
System.out.println("账户失效");
return "fail";
} catch (ConcurrentAccessException e) {
System.out.println("竞争次数过多");
return "fail";
} catch (ExcessiveAttemptsException e) {
System.out.println("尝试次数过多");
return "fail";
} catch (UnknownAccountException e) {
System.out.println("用户名不正确");
return "fail";
} catch (IncorrectCredentialsException e) {
System.out.println("密码不正确");
return "fail";
} catch (ExpiredCredentialsException e) {
System.out.println("凭证过期");
return "fail";
}
}
使用散列算法加密认证
散列算法(Hash Algorithm)是一种用于数据加密、数据完整性验证和数字签名等领域的算法。它的主要目标是将任意长度的数据映射为固定长度的散列值,以便于数据的处理和比较。常见的散列算法包括 MD5、SHA-1、SHA-256 等。这些算法在计算机安全领域得到广泛应用,例如用于文件校验、密码存储、数字签名等。
例如,密码“zhangsan”会产生散列值“23ds2i1sdda97sad8u8asda0d”,但通过 md5 解密网站可以很容易地通过散列值获取到密码“zhangsan”。因此,在加密时我们可以添加一些只有系统知道的干扰数据,这些干扰数据被称为“盐”,并且可以进行多次加密,这样生成的散列值就更难被破解了。
1.修改数据库和实体类,添加盐字段,并修改数据库用户密码为加盐加密后的数据。
@Data
public class Users{
private Integer uid;
private String username;
private String password;
private String salt;
}
2.修改自定义Realm
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserInfoMapper userInfoMapper;
// 自定义认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.获取用户输入的用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
// 2.根据用户名查询用户
QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
Users users = usersMapper.selectOne(wrapper);
// 3.将查询到的用户封装为认证信息
if (users == null) {
throw new UnknownAccountException("账户不存在");
}
/**
* 参数1:用户
* 参数2:密码
* 参数3:盐
* 参数4:Realm名
*/
return new SimpleAuthenticationInfo(users,
users.getPassword(),
ByteSource.Util.bytes(users.getSalt()),
"myRealm");
}
// 自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
3.在注册自定义Realm时添加加密算法
// 配置加密算法
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//加密算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//加密的次数
hashedCredentialsMatcher.setHashIterations(5);
return hashedCredentialsMatcher;
}
// Realm
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
MyRealm myRealm = new MyRealm();
// 设置加密算法
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myRealm;
}
过滤器
通过配置过滤器,我们可以规定哪些资源是需要认证后才能访问的,哪些资源是不需要认证便可以访问的。Shiro内置了很多过滤器:
过滤器 | 说明 |
---|---|
anon | 配置不需要登录即可访问的资源 |
authc | 配置登录认证后才可以访问的资源 |
user | 配置登录认证或“记住我”认证后才可以访问的资源 |
// 配置过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
// 1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
// 2.过滤器工厂设置SecurityManager
filterFactory.setSecurityManager(securityManager);
// 3.设置shiro的拦截规则
Map<String,String> filterMap=new HashMap<>();
// 不拦截的资源
filterMap.put("/login.html","anon");
filterMap.put("/fail.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/css/**","anon");
// 其余资源都需要用户认证
filterMap.put("/**","authc");
// 4.将拦截规则设置给过滤器工厂
filterFactory.setFilterChainDefinitionMap(filterMap);
// 5.登录页面
filterFactory.setLoginUrl("/login.html");
return filterFactory;
}
获取认证数据
通过subject.getPrincipal();我们可以获得认证后的用户数据
@RequestMapping("/user/getUsername")
@ResponseBody
public String getUsername(){
Subject subject = SecurityUtils.getSubject();
// 获取认证数据
Users users = (Users)subject.getPrincipal();
return users.getUsername();
}
Shiro会话
Shiro提供了完整的企业级会话管理功能,并且不依赖于Web容器,不管JavaSE还是JavaEE环境都可以使用。
// 使用Shiro提供的会话对象
@RequestMapping("/user/session")
@ResponseBody
public void session(){
// 1.获取Subject
Subject subject = SecurityUtils.getSubject();
// 2.获取会话
Session session = subject.getSession();
// 会话id
System.out.println("会话id:"+session.getId());
// 会话的主机地址
System.out.println("会话的主机地址:"+session.getHost());
// 设置会话过期时间
session.setTimeout(1000*10);
// 获取会话过期时间
System.out.println("会话过期时间:"+session.getTimeout());
// 会话开始时间
System.out.println("会话开始时间:"+session.getStartTimestamp());
// 会话最后访问时间
System.out.println("会话最后访问时间:"+session.getLastAccessTime());
// 会话设置数据
session.setAttribute("name","百战");
}
@RequestMapping("/user/getSession")
@ResponseBody
public void getSession(){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
System.out.println(session.getAttribute("name"));
}
会话管理器
会话管理器可以对会话对象进行配置和监听
1.创建会话监听器
@Component
public class MySessionListener implements SessionListener {
//会话创建时触发
@Override
public void onStart(Session session) {
System.out.println("会话创建:" + session.getId());
}
//会话过期时触发
@Override
public void onExpiration(Session session) {
System.out.println("会话过期:" + session.getId());
}
//退出/会话过期时触发
@Override
public void onStop(Session session) {
System.out.println("会话停止:" + session.getId());
}
}
2.在会话管理器中配置会话监听器
// 会话管理器
@Bean
public SessionManager sessionManager(MySessionListener sessionListener) {
// 创建会话管理器
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 创建会话监听器集合
List<SessionListener> listeners = new ArrayList();
listeners.add(sessionListener);
// 将监听器集合设置到会话管理器中
sessionManager.setSessionListeners(listeners);
// 全局会话超时时间(单位毫秒),默认30分钟,设置为5秒
sessionManager.setGlobalSessionTimeout(5*1000);
// 是否开启删除无效的session对象,默认为true
sessionManager.setDeleteInvalidSessions(true);
// 是否开启定时调度器进行检测过期session,默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
return sessionManager;
}
3.在SecurityManager中配置会话管理器
@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm,MyRealm2 myRealm2,SessionManager sessionManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义Realm放入SecurityManager中
// securityManager.setRealm(myRealm);
// 设置Realm管理者(需要设置在Realm之前)
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList();
realms.add(myRealm);
// realms.add(myRealm2);
securityManager.setRealms(realms);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
退出登录
当退出登陆后,要删除会话对象和认真数据
1.编写退出登录控制器
@RequestMapping("/user/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
// 退出登录
subject.logout();
// 退出后跳转到登录页
return "redirect:/login";
}
2.在主页面添加退出登录按钮
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
<h1>主页面</h1>
<h2><a href="/user/logout">退出登录</a></h2>
</body>
</html>
记住我
Shiro的记住我功能可以在用户登录成功后,关闭浏览器,下次再访问系统资源时,无需再执行登录操作。
实现Shiro的记住我功能,一般是通过将用户的一些基本信息(密码)存入浏览器的Cookie,下次登录时优先验证Cookie,后端做处理,以此来实现记住密码的功能。
需注意,若网站对安全性要求较高,一般不建议开启记住密码功能,因为Cookie是保存在本机电脑浏览器里,其他用户可能会使用此电脑拷走Cookie,导入其他电脑继续使用账号登录。
1.序列化所有实体类
@Data
public class Users implements Serializable {
private Integer uid;
private String username;
private String password;
private String salt;
}
2.配置Cookie生成器和记住我管理器
// Cookie生成器
@Bean
public SimpleCookie simpleCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// Cookie有效时间,单位:秒
simpleCookie.setMaxAge(20);
return simpleCookie;
}
// 记住我管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
// Cookie生成器
cookieRememberMeManager.setCookie(simpleCookie);
// Cookie加密的密钥
cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));
return cookieRememberMeManager;
}
@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm,
MyRealm2 myRealm2,
SessionManager sessionManager,
CookieRememberMeManager rememberMeManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义Realm放入SecurityManager中
// securityManager.setRealm(myRealm);
// 设置Realm管理者(需要设置在Realm之前)
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList();
realms.add(myRealm);
// realms.add(myRealm2);
securityManager.setRealms(realms);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
3.修改登录表单
<form class="form" action="/user/login" method="post">
<input type="text" placeholder="用户名" name="username">
<input type="password" placeholder="密码" name="password">
<input type="checkbox" name="rememberMe" value="on">记住我<br>
<button type="submit" id="login-button">登录</button>
</form>
4.修改登录控制器
@RequestMapping("/user/login")
public String login(String username, String password,String rememberMe) {
try {
usersService.userLogin(username, password,rememberMe);
return "main";
} catch (DisabledAccountException e) {
System.out.println("账户失效");
return "fail";
} catch (ConcurrentAccessException e) {
System.out.println("竞争次数过多");
return "fail";
} catch (ExcessiveAttemptsException e) {
System.out.println("尝试次数过多");
return "fail";
} catch (UnknownAccountException e) {
System.out.println("用户名不正确");
return "fail";
} catch (IncorrectCredentialsException e) {
System.out.println("密码不正确");
return "fail";
} catch (ExpiredCredentialsException e) {
System.out.println("凭证过期");
return "fail";
}
}
5.修改登录Service
@Service
public class UsersService {
@Autowired
private DefaultWebSecurityManager securityManager;
public void userLogin(String username,String password,String rememberMe) throws AuthenticationException {
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
if (rememberMe != null){
// 如果用户选择记住我,则生成记住我Cookie
token.setRememberMe(true);
}
subject.login(token);
}
}
6.配置过滤器,配置可以通过“记住我”访问的资源。
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
// 1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
// 2.过滤器工厂设置SecurityManager
filterFactory.setSecurityManager(securityManager);
// 3.设置shiro的拦截规则
Map<String,String> filterMap=new HashMap<>();
// 不拦截的资源
filterMap.put("/login.html","anon");
filterMap.put("/fail.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/static/**","anon");
// 其余资源都需要认证,authc过滤器表示需要认证才能进行访问; user过滤器表示配置记住我或认证都可以访问
// filterMap.put("/**","authc");
filterMap.put("/user/pay","authc");
filterMap.put("/**", "user");
// 4.将拦截规则设置给过滤器工厂
filterFactory.setFilterChainDefinitionMap(filterMap);
// 5.登录页面
filterFactory.setLoginUrl("/login.html");
return filterFactory;
}
7.编写支付控制器
// 支付
@RequestMapping("/user/pay")
@ResponseBody
public String pay(){
return "支付功能";
}
总结
提示:这里对文章进行总结:
Shiro是一个简单且强大的框架,提供了全面的安全管理服务,如认证、授权、加密和会话管理等。总的来说,Shiro的认证功能可以通过realm来实现,并且可以通过自定义realm来满足不同的认证需求。在使用Shiro时,建议遵循最佳实践,并根据你的项目需求进行适当的配置和定制,以确保安全性和灵活性。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!