在现代Web开发中,安全性是至关重要的。Shiro是一个强大的Java安全框架,提供了认证、授权、加密和会话管理等功能。本文将详细介绍如何在Spring Boot项目中集成Shiro进行用户认证。
一、项目准备
首先,我们需要创建一个Spring Boot项目,并添加必要的依赖。在pom.xml
文件中添加Shiro、MyBatis、数据库驱动和Thymeleaf等依赖:
<dependencies> | |
<!-- Spring Boot Starter Web --> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
<!-- MyBatis Starter --> | |
<dependency> | |
<groupId>org.mybatis.spring.boot</groupId> | |
<artifactId>mybatis-spring-boot-starter</artifactId> | |
<version>1.3.1</version> | |
</dependency> | |
<!-- Thymeleaf Starter --> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-thymeleaf</artifactId> | |
</dependency> | |
<!-- Shiro Spring Integration --> | |
<dependency> | |
<groupId>org.apache.shiro</groupId> | |
<artifactId>shiro-spring</artifactId> | |
<version>1.8.0</version> | |
</dependency> | |
<!-- Database Driver (e.g., MySQL) --> | |
<dependency> | |
<groupId>mysql</groupId> | |
<artifactId>mysql-connector-java</artifactId> | |
<scope>runtime</scope> | |
</dependency> | |
</dependencies> |
二、数据库和实体类
创建一个数据库和用户表,用于存储用户信息。例如,一个简单的用户表结构如下:
CREATE TABLE user ( | |
id INT AUTO_INCREMENT PRIMARY KEY, | |
username VARCHAR(50) NOT NULL UNIQUE, | |
password VARCHAR(100) NOT NULL, | |
salt VARCHAR(50), | |
realname VARCHAR(100) | |
); |
然后,在Spring Boot项目中创建一个对应的实体类User
,并使用JPA注解进行映射:
import javax.persistence.*; | |
@Entity | |
@Table(name = "user") | |
public class User { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
private Integer id; | |
@Column(name = "username") | |
private String username; | |
@Column(name = "password") | |
private String password; | |
@Column(name = "salt") | |
private String salt; | |
@Column(name = "realname") | |
private String realname; | |
// Getters and Setters | |
} |
三、配置Shiro
创建一个Shiro配置类ShiroConfig
,配置Shiro的安全管理器、Realm和过滤器链:
import org.apache.shiro.authc.credential.HashedCredentialsMatcher; | |
import org.apache.shiro.mgt.SecurityManager; | |
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; | |
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
@Configuration | |
public class ShiroConfig { | |
@Bean | |
public SecurityManager securityManager() { | |
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); | |
securityManager.setRealm(myRealm()); | |
return securityManager; | |
} | |
@Bean | |
public MyRealm myRealm() { | |
MyRealm realm = new MyRealm(); | |
realm.setCredentialsMatcher(credentialsMatcher()); | |
return realm; | |
} | |
@Bean | |
public HashedCredentialsMatcher credentialsMatcher() { | |
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); | |
matcher.setHashAlgorithmName("SHA-256"); | |
matcher.setHashIterations(1024); | |
return matcher; | |
} | |
@Bean | |
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { | |
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); | |
shiroFilterFactoryBean.setSecurityManager(securityManager); | |
shiroFilterFactoryBean.setLoginUrl("/login"); | |
shiroFilterFactoryBean.setSuccessUrl("/"); | |
shiroFilterFactoryBean.setUnauthorizedUrl("/403"); | |
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); | |
filterChainDefinitionMap.put("/css/**", "anon"); | |
filterChainDefinitionMap.put("/js/**", "anon"); | |
filterChainDefinitionMap.put("/fonts/**", "anon"); | |
filterChainDefinitionMap.put("/img/**", "anon"); | |
filterChainDefinitionMap.put("/login", "anon"); | |
filterChainDefinitionMap.put("/logout", "logout"); | |
filterChainDefinitionMap.put("/**", "authc"); | |
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); | |
return shiroFilterFactoryBean; | |
} | |
} |
四、自定义Realm
创建一个自定义的Realm类MyRealm
,用于处理用户的认证和授权逻辑:
import org.apache.shiro.authc.*; | |
import org.apache.shiro.authz.AuthorizationInfo; | |
import org.apache.shiro.authz.SimpleAuthorizationInfo; | |
import org.apache.shiro.realm.AuthorizingRealm; | |
import org.apache.shiro.subject.PrincipalCollection; | |
import org.apache.shiro.util.ByteSource; | |
import org.springframework.beans.factory.annotation.Autowired; | |
public class MyRealm extends AuthorizingRealm { | |
@Autowired | |
private UserService userService; | |
@Override | |
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { | |
String username = (String) principals.getPrimaryPrincipal(); | |
User user = userService.findByUsername(username); | |
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); | |
authorizationInfo.addRoles(user.getRoles()); | |
authorizationInfo.addStringPermissions(user.getPermissions()); | |
return authorizationInfo; | |
} | |
@Override | |
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { | |
UsernamePasswordToken upToken = (UsernamePasswordToken) token; | |
String username = upToken.getUsername(); | |
User user = userService.findByUsername(username); | |
if (user == null) { | |
throw new UnknownAccountException("用户不存在"); | |
} | |
ByteSource salt = ByteSource.Util.bytes(user.getSalt()); | |
return new SimpleAuthenticationInfo(username, user.getPassword(), salt, getName()); | |
} | |
} |
五、业务层和服务层
创建一个UserService
接口和实现类,用于访问数据库中的用户信息:
import org.springframework.stereotype.Service; | |
import java.util.List; | |
public interface UserService { | |
User findByUsername(String username); | |
// 其他方法... | |
} | |
@Service | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserMapper userMapper; | |
@Override | |
public User findByUsername(String username) { | |
List<User> users = userMapper.findAll(); | |
for (User user : users) { | |
if (user.getUsername().equals(username)) { | |
return user; | |
} | |
} | |
return null; | |
} | |
// 其他实现... | |
} |
创建一个UserMapper
接口和对应的XML映射文件,用于MyBatis操作数据库:
import org.apache.ibatis.annotations.Mapper; | |
import java.util.List; | |
@Mapper | |
public interface UserMapper { | |
List<User> findAll(); | |
} |
UserMapper.xml
:
<?xml version="1.0" encoding="UTF-8" ?> | |
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
<mapper namespace="com.example.demo.mapper.UserMapper"> | |
<select id="findAll" resultType="com.example.demo.beans.User"> | |
SELECT * FROM user | |
</select> | |
</mapper> |
六、控制器和视图
创建一个控制器类,用于处理登录请求和受保护的资源请求:
package com.bdqn.controller; import com.bdqn.pojo.Right; import com.bdqn.pojo.Role; import com.bdqn.pojo.User; import com.bdqn.service.RoleService; import com.bdqn.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.PostMapping; import java.util.List; /** * 登录注销相关控制器 * * @author LILIBO * @since 2024/9/21 */ @Controller public class IndexController { @Resource private UserService userService; @Resource private RoleService roleService; /** * 去登录页 */ @GetMapping("/login") public String toLogin() { return "login"; } /** * 执行登录操作 */ @PostMapping("/login") public String doLogin(HttpSession session, String usrName, String usrPassword, Model model) { // User loginUser = userService.login(usrName, usrPassword); // session.setAttribute("loginUser", loginUser); // return "redirect:/main"; try { UsernamePasswordToken token=new UsernamePasswordToken(usrName,usrPassword); Subject subject= SecurityUtils.getSubject(); subject.login(token); // 认证(登录)成功 User user=(User) subject.getPrincipal(); Role role = user.getRole(); List<Right> rights = roleService.getRigthsByRoleId(role); role.getRights().addAll(rights); session.setAttribute("loginUser",user); return "redirect:/main"; }catch (UnknownAccountException | IncorrectCredentialsException e){ model.addAttribute("message","用户名或密码失败,登录失败!"); return "login"; }catch (LockedAccountException e){ model.addAttribute("message","用户被禁用,登录失败!"); return "login"; }catch (AuthenticationException e){ model.addAttribute("message","认证异常,登录失败!"); return "login"; } } /** * 跳转到主页 */ @GetMapping("/main") public String toMain() { return "main"; } /** * 退出登录 */ @GetMapping("/logout") public String logout(HttpSession session) { session.removeAttribute("loginUser"); SecurityUtils.getSubject().logout(); return "redirect:/login"; } @GetMapping("/403") public String to403(){ return "403"; } }