Spirng Boot+Shiro+Druid+Mybatis Plus+Mysql搭建基础框架实现登录用户认证
开发环境
- JDK1.8
- Intellij IDEA 2019.1
- Win 10 家庭版
项目创建
- 打开idea Create New Project

- 选择Spring Initializr

- 录入项目名

- 选择初始化的引入包





自动构建的代码结构,框住的文件为非必要文件,可以删除,我开发中都是直接删掉的
初始的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置数据库,连接池信息
1.引入Durid+Mybatis Plus+Mysql
引入Druid数据库连接池
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>
引入Mybatis Plus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>
引入Mysql驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 配置 连接池及数据源
在applicaiton.yml增加下列配置
注意、初始化生成的application.properties,有可以直接在application.properties增加如下配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
filters: stat,wall,log4j2,config
# 连接池最大数
max-active: 20
# 第一次初始化链接数
initial-size: 5
# 最小空闲数
min-idle: 5
validation-query: select '*'
test-while-idle: true
3. 配置mybatis plus
mybatis-plus:
global-config:
# 屏蔽mp banner图
banner: false
也可以不关闭banner图,我实在觉得它碍眼?
在启动类增加注解
@MapperScan("com.example.demo.**.mapper")
注意:该处有一个坑,不能把路径扫描到com.example.demo.**
无法正确扫描的Mapper,必须是在**.后面增加你的实际放Dao的包名,这里我的包名都是**.mapper,你可以用其他包名

该注解作用是用作扫描Dao接口
4. 引入工具包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>RELEASE</version>
</dependency>
编写登录测试代码
User 用户类
package com.example.demo.login.model;
import lombok.Data;
/**
* 用户信息类
* com.example.demo.login.model
* User
*
* @author LiuJingPing
* @date 2019/7/31 10:53
*/
@Data
public class User {
private String username;
private String password;
}
UserMapper
package com.example.demo.login.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.login.model.User;
/**
* 用户Mapper
* com.example.demo.login.mapper
* UserMapper
*
* @author LiuJingPing
* @date 2019/7/31 10:55
*/
public interface UserMapper extends BaseMapper<User> {
}
UserService
package com.example.demo.login.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.login.model.User;
/**
* 用户Service
* com.example.demo.login.service
* UserService
*
* @author LiuJingPing
* @date 2019/7/31 10:56
*/
public interface UserService extends IService<User> {
/**
* 通过UserName获取用户信息
*
* @param username 用户名
* @return User
* @author LiuJinPing
* @date 2019/7/31 10:59
*/
User getByUsername(String username);
}
UserServiceImpl
package com.example.demo.login.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.login.mapper.UserMapper;
import com.example.demo.login.model.User;
import com.example.demo.login.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 用户ServiceImpl
* com.example.demo.login.service.impl
* UserServiceImpl
*
* @author LiuJingPing
* @date 2019/7/31 10:57
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User getByUsername(String username) {
if (StringUtils.isEmpty(username)) {
return null;
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
List<User> list = list(queryWrapper);
if (CollectionUtils.isNotEmpty(list)) {
// 从设计上来说,一般用户名是唯一的,所以list的大小一般为1
return list.get(0);
}
return null;
}
}
LoginController
package com.example.demo.login.controller;
import com.example.demo.login.model.User;
import com.example.demo.login.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 登录控制器
* com.example.demo.login.controller
* LoginController
*
* @author LiuJingPing
* @date 2019/7/31 10:54
*/
@Controller
@RequestMapping("/")
public class LoginController {
private final UserService userService;
public LoginController(UserService userService) {
this.userService = userService;
}
@PostMapping("/doLogin")
public String doLogin(User user) {
if (user != null) {
//如果账号密码正确跳转到index
User getUser = userService.getByUsername(user.getUsername());
if (getUser != null && StringUtils.equals(user.getPassword(), getUser.getPassword())) {
return "index";
}
}
//否则跳转到密码错误页面
return "passwordError";
}
@RequestMapping("/login")
public String login() {
return "login";
}
//默认跳转首页
@RequestMapping("/")
public String index1() {
return "login";
}
@RequestMapping("/index")
public String index() {
return "index";
}
}
建库 建表 插入测试用户
CREATE DATABASE `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
CREATE TABLE `user` (
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`passwrod` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('jimbo', '123456');
编写页面
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录测试页面</title>
</head>
<body>
<form method="post" action="/doLogin">
用户名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
<input type="submit">
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1 style="text-align: center">Hello World</h1>
</body>
</html>
passwordError.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>密码错误</title>
</head>
<body>
<h1 style="text-align: center;color:red;">登录失败:用户名或密码错误</h1>
</body>
</html>
启动测试
到目前为止,并未引入Shiro,所以 /index 可以直接访问,现在实现的功能是账号密码正确时,跳转Index 否则跳转passwordError
applicaiton.yml增加启动端口
server:
port: 8080
启动项目 访问http://localhost:8080/
输入正确的账号,点击提交

为了方便测试,密码输入框用的明文,实际开发应该使用type=password

正确跳转到index.html
返回,输入错误的账号密码

成功跳转到错误页面
到此,基本功能开发完成
但是还未集成Shiro的身份认证
现在,用户可以通过浏览器地址访问http://localhost:8080/index 访问到首页,在实际开发中不允许这样的情况
接下来开始集成Shiro
引入Shiro
pom.xml增加引入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
配置Shiro
自定义realm
package com.example.demo.conifg.shiro;
import com.example.demo.login.model.User;
import com.example.demo.login.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 自定义Realm
* com.example.demo.configuration.shiro
* CustomizeRealm
*
* @author LiuJingPing
* @date 2019/7/31 11:35
*/
public class CustomizeRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 权限认证 暂时不使用
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 身份认证 登录认证
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取登录令牌用户名
String username = (String) token.getPrincipal();
// 获取用户信息
User user = userService.getByUsername(username);
if (user == null) {
throw new AuthenticationException();
}
return new SimpleAuthenticationInfo(username, user.getPassword(), "pb");
}
}
Shiro配置类
package com.example.demo.conifg.shiro;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* Shiro配置类
* com.example.demo.configuration.shiro
* ShiroConfiguration
*
* @author LiuJingPing
* @date 2019/7/31 11:40
*/
@Configuration
public class ShiroConfiguration {
@Bean("customizeRealm")
public Realm realm() {
return new CustomizeRealm();
}
@Bean("sessionManager")
public SessionManager sessionManager() {
return new DefaultWebSessionManager();
}
@Bean("securityManager")
public SecurityManager securityManager(SessionManager sessionManager, Realm customizeRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
securityManager.setRealm(customizeRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//必须设置securityManager
filterFactoryBean.setSecurityManager(securityManager);
//增加请求地址的认证
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
//未登录跳转页面
filterFactoryBean.setLoginUrl("/login");
//登录成功跳转页面
filterFactoryBean.setSuccessUrl("/index");
return filterFactoryBean;
}
private Map<String, String> filterChainDefinitionMap() {
//anon 不需要认证
//authc 需要认证
//当访问时是从上往下认证的,当URL匹配到第一个的时候,不会继续向下匹配了
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/doLogin", "anon");
filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("/logout", "logout");
return filterChainDefinitionMap;
}
}
重写登录方法
@PostMapping("/doLogin")
public String doLogin(User user) {
if (user != null) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
// 如果账号密码都正确不会抛出异常
// shiro 认证 当认证失败时会抛出异常
//try catch 处理掉异常
subject.login(token);
return "index";
} catch (Exception e) {
return "passwordError";
}
}
//否则跳转到密码错误页面
return "passwordError";
}
当subject.login()时,会调用我们自定义的realm的doGetAuthenticationInfo方法进行身份认证,认证处理都在realm里面,所以不需要在这里对比密码
启动测试
首先测试,未经过身份认证的是否访问index页面
访问http://localhost:8080/index
因为没有认证的身份,所以会自动重定向到/login,
即在ShiroConfiguration中配置的
//未登录跳转页面
filterFactoryBean.setLoginUrl("/login");

输入账号密码,进行登录测试
跳转页面和之前一致,但是在登录成功后,可直接输入http://localhost:8080/index进行首页访问,在登录失败后,访问该页面会返回到登录页面
总结
- springboot 已经简化了spring的配置,在开发中,我们可以减少xml进行配置,在代码中大量使用注解的形式引入bean
- 为什么要使用mybatis plus?因为mybatis plus 在mybatis的基础上简化了很多,而却mybatis plus 提供了许多的通用方法,通过继承可减少很多代码的编写,同时mybatis plus提供lambda表达式进行条件编写,在后期若数据库字段改变,我们只需要在实体类上增加注解即可,不用修改业务代码,mybatis plus只在mybatis基础上做了增强,可完全移植。注意:lambda表达式方式只能在jdk1.8及以上使用
- Shiro相对SpringSecurity 来说更简单一点,功能也能满足日常使用,Shiro也可通过自定义SessionDao将Session写入Redis进行session共享
如果本文章有什么问题可以提出,我会尽快修改,如果你有什么更好的实现方式,请在文章后留言
最后,附上源码,可直接下载使用idea运行
链接:https://pan.baidu.com/s/12m5_1dGvM7-3etjsmjOcTA
提取码:1n1t
514

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



