一、概述
使用SpringBoot集成shiro实现登录注册功能,将这部分交给shiro控制,通过shiro的认证来实现
二、shiro_springboot_mybatis_登录注册
功能划分:
- 登录
- 注册
代码分为:
- mybatis - mapper.xml
- entity - User
- realm - UserRealm 数据域
- config - ShiroConfig Shiro配置类
- dao - UserDao
- service - UserService (interface) UserServiceImpl (Class)
- controller - UserController
- utils - ApplicationContextUtils (获取Bean) SaltUtils (生成随机盐)
- webapp - jsp 前端展示的登录 注册 主页页面
项目包结构图 - 图示:
image-20220222163741812.png
第一步
先把环境搭建好
导入依赖:
<!-- shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<!-- 解析jsp依赖-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>9.0.41</version>
</dependency>
<!-- jstl标签依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
SpringBoot配置文件
spring.application.name=shiro
server.servlet.context-path=/shiro
server.port=8899
#视图解析
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
#datesource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?Encodeing=UTF-8
#mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
第二步
建表 and 写持久层代码
| t_user | CREATE TABLE `t_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`salt` varchar(16) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 |
写Mybatis文件
<?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.geekrose.shiro.dao.UserDao">
<sql id="userBody">
id,username,password,salt
</sql>
<insert id="save" parameterType="com.geekrose.shiro.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user
<trim prefix="values ( " suffix=")" suffixOverrides=",">
#{id},
<if test="username != null">
#{username},
</if>
<if test="password != null">
#{password},
</if>
<if test="salt != null">
#{salt},
</if>
</trim>
</insert>
<select id="findUserByName" resultType="com.geekrose.shiro.entity.User" parameterType="java.lang.String">
select <include refid="userBody"></include>
from t_user where username = #{username}
</select>
</mapper>
第三步
写业务层代码
接口:
public interface UserService {
void register(User user);
User findUserByName(String name);
}
实现类:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
public void register(User user){
// 添加账号密码随机盐
String salt = SaltUtils.getSalt(8);
user.setSalt(salt);
// 生成随机密码
Md5Hash result = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(result.toHex());
System.out.println(user);
userDao.save(user);
}
public User findUserByName(String name) {
User user = userDao.findUserByName(name);
return user;
}
}
第四步
写工具类代码
获取Bean
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public static Object getBean(String beanName){
Object bean = context.getBean(beanName);
return bean;
}
}
生成随机盐
public class SaltUtils {
public static String getSalt(int length){
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,.;[]/'!@#$%^&*()-=".toCharArray();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < length; i++) {
buffer.append(chars[new Random().nextInt(chars.length)]);
}
return buffer.toString();
}
public static void main(String[] args) {
System.out.println(getSalt(4));
}
}
第五步
编写控制层代码
@Controller
@RequestMapping("user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("register")
public String doRegister(User user){
System.out.println(user);
try {
userService.register(user);
}catch (Exception e){
e.printStackTrace();
return "redirect:/register.jsp";
}
return "redirect:/login.jsp";
}
@RequestMapping("login")
public String doLogin(String username,String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
try {
subject.login(token);
return "redirect:/index.jsp";
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("账号错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
return "redirect:/login.jsp";
}
@RequestMapping("logout")
public String doLogout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
}
第六步
编写shiro相关代码
ShiroConfig配置类
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, String> map = new HashMap<>();
map.put("/login.jsp","anon");
map.put("/register.jsp","anon");
map.put("/user/login","anon");
map.put("/user/register","anon");
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm getRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(getRealm);
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1024);
matcher.setHashAlgorithmName("md5");
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}
}
UserRealm 数据域
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findUserByName(principal);
if (!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
}
第七步
编写前端显示的jsp页面
index.jsp 主页
<%@page contentType="text/html; utf-8" language="java" isELIgnored="false" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>Hello Shiro index page to SpringBoot</h2>
<h3><a href="${pageContext.request.contextPath}/user/logout">登出shiro</a></h3>
<ul>
<li><a href="">用户模块</a></li>
<li><a href="">产品模块</a></li>
<li><a href="">组件模块</a></li>
<li><a href="">竞价模块</a></li>
</ul>
</body>
</html>
login.jsp 登录
<%@page contentType="text/html; utf-8" language="java" isELIgnored="false" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>Hello Shiro login page to SpringBoot</h2>
<form action="${pageContext.request.contextPath}/user/login" method="post">
username: <input type="text" name="username"> <br>
password: <input type="text" name="password"> <br>
<input type="submit" value="登录">
</form>
<h3><a href="register.jsp">前往注册</a></h3>
</body>
</html>
register.jsp 注册
<%@page contentType="text/html; utf-8" language="java" isELIgnored="false" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>Hello Shiro register page to SpringBoot</h2>
<form action="${pageContext.request.contextPath}/user/register" method="post">
username: <input type="text" name="username"> <br>
password: <input type="text" name="password"> <br>
<input type="submit" value="立即注册">
</form>
</body>
</html>
最后显示效果:
登录:
image-20220222164958271.png
注册:
image-20220222165009869.png
主页:
image-20220222165028594.png
三、分析
shiro部分
这里在ShiroConfig配置类中关于数据域的注入中使用了md5加密、加盐、散列操作
-
控制层的认证部分交给Shiro管理
登录
subject.login(token);
登出
subject.logout();
注册:通过向用户表插入 用户记录方式
插入密码时 使用 md5加密 + 加盐 + 散列方式 后存储
并且将随机盐存储在内
-
登录的校验
因为相同明文 进行md5 加密生成的密文一定是唯一的,所以可以进行 md5加密后和数据库中的密文对比,相同就登入成功(重定向到主页)