在现代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";
}
}

3621

被折叠的 条评论
为什么被折叠?



