SpringBoot + Layui + JustAuth +Mybatis-plus实现可第三方登录的简单后台管理系统
1. 简介
在之前博客:SpringBoot基于JustAuth实现第三方授权登录 和 SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)上改造,除了原始的用户名和密码登录外,增加第三方登录认证。
2. 改造流程
- 在登录页增加第三方系统登录链接
- 第三方系统注册应用,并记录
API Key和Secret Key - 将
API Key、Secret Key和回调地址添加到系统配置文件 - 改造回调方法,判断授权用户与系统用户是否绑定
- 若已绑定,则跳转到首页
- 若未绑定,则跳转到绑定页进行绑定,绑定完成后跳转到首页
3. 流程图

4. 改造代码
下载示例工程:spring-boot-justauth-demo 和 :spring-boot-layui-demo,以spring-boot-layui-demo为基础,进行改造。
- 授权用户表增加user_id字段,并在本系统数据库中创建
DROP TABLE IF EXISTS `t_ja_user`;
CREATE TABLE `t_ja_user` (
`uuid` varchar(64) NOT NULL COMMENT '用户第三方系统的唯一id',
`username` varchar(100) NULL DEFAULT NULL COMMENT '用户名',
`nickname` varchar(100) NULL DEFAULT NULL COMMENT '用户昵称',
`avatar` varchar(255) NULL DEFAULT NULL COMMENT '用户头像',
`blog` varchar(255) NULL DEFAULT NULL COMMENT '用户网址',
`company` varchar(50) NULL DEFAULT NULL COMMENT '所在公司',
`location` varchar(255) NULL DEFAULT NULL COMMENT '位置',
`email` varchar(50) NULL DEFAULT NULL COMMENT '用户邮箱',
`gender` varchar(10) NULL DEFAULT NULL COMMENT '性别',
`remark` varchar(500) NULL DEFAULT NULL COMMENT '用户备注(各平台中的用户个人介绍)',
`source` varchar(20) NULL DEFAULT NULL COMMENT '用户来源',
`user_id` int(0) NULL DEFAULT NULL COMMENT '系统用户ID',
PRIMARY KEY (`uuid`) USING BTREE
) ENGINE = InnoDB COMMENT = '授权用户';
- 将JustAuth授权用户相关的Entity、Service、Service Impl、Mapper拷贝到系统,Entity添加userId属性,并添加set/get方法
import java.io.Serializable;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
/**
* 授权用户信息
*
* @author CL
*
*/
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_ja_user")
@EqualsAndHashCode(callSuper = false)
public class JustAuthUser extends AuthUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户第三方系统的唯一id。在调用方集成该组件时,可以用uuid + source唯一确定一个用户
*/
@TableId(type = IdType.INPUT)
private String uuid;
/**
* 用户授权的token信息
*/
@TableField(exist = false)
private AuthToken token;
/**
* 第三方平台返回的原始用户信息
*/
@TableField(exist = false)
private JSONObject rawUserInfo;
/**
* 系统用户ID
*/
@Setter
@Getter
private Integer userId;
/**
* 自定义构造函数
*
* @param authUser 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
*/
public JustAuthUser(AuthUser authUser) {
super(authUser.getUuid(), authUser.getUsername(), authUser.getNickname(), authUser.getAvatar(),
authUser.getBlog(), authUser.getCompany(), authUser.getLocation(), authUser.getEmail(),
authUser.getRemark(), authUser.getGender(), authUser.getSource(), authUser.getToken(),
authUser.getRawUserInfo());
}
}
- 配置文件添加配置
- 修改端口为8443(与注册应用时一致)
- 添加redis配置(若justauth.cache.type配置使用default,则忽略此配置)
- 将第三方系统认证相关配置拷贝到系统配置文件中,并修改相关配置
- 可参考以下配置内容
server:
port: 8443
servlet:
session:
timeout: 1800s
spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/layuidemo?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
# redis:
# host: 127.0.0.1
# port: 6379
# password: 123456
# # 连接超时时间(记得添加单位,Duration)
# timeout: 2000ms
# # Redis默认情况下有16个分片,这里配置具体使用的分片
# database: 0
# lettuce:
# pool:
# # 连接池最大连接数(使用负值表示没有限制) 默认 8
# maxActive: 8
# # 连接池中的最大空闲连接 默认 8
# maxIdle: 8
thymeleaf:
prefix: classpath:/view/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
# 生产环境设置true
cache: false
# Mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: AUTO
configuration:
# 打印sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志配置
logging:
level:
com.xkcoding: debug
# 第三方系统认证
justauth:
enabled: true
type:
BAIDU:
client-id: xxxxxx
client-secret: xxxxxx
redirect-uri: http://127.0.0.1:8443/oauth/baidu/callback
GITEE:
client-id: xxxxxx
client-secret: xxxxxx
redirect-uri: http://127.0.0.1:8443/oauth/gitee/callback
cache:
# 缓存类型(default-使用JustAuth内置的缓存、redis-使用Redis缓存、custom-自定义缓存)
type: default
# 缓存前缀,目前只对redis缓存生效,默认 JUSTAUTH::STATE::
prefix: 'JUATAUTH::STATE::'
# 超时时长,目前只对redis缓存生效,默认3分钟
timeout: 3m
# 信息安全
security:
web:
excludes:
- /login
- /logout
- /oauth/**
- /images/**
- /jquery/**
- /layui/**
xss:
enable: true
excludes:
- /login
- /logout
- /images/*
- /jquery/*
- /layui/*
sql:
enable: true
excludes:
- /images/*
- /jquery/*
- /layui/*
csrf:
enable: true
excludes:
- 重构AuthController,修改回调方法,增加用户绑定方法
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.c3stones.auth.entity.JustAuthUser;
import com.c3stones.auth.service.JustAuthUserService;
import com.c3stones.common.response.Response;
import com.c3stones.sys.entity.User;
import com.c3stones.sys.service.UserService;
import com.xkcoding.justauth.AuthRequestFactory;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
/**
* 授权Controller
*
* @author CL
*
*/
@Slf4j
@Controller
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private AuthRequestFactory factory;
@Autowired
private JustAuthUserService justAuthUserService;
@Autowired
private UserService userService;
/**
* 登录
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param response
* @throws IOException
*/
@GetMapping(value = "/login/{type}")
public void login(@PathVariable String type, HttpServletResponse response) throws IOException {
AuthRequest authRequest = factory.get(type);
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
/**
* 登录回调
*
* @param type 第三方系统类型,例如:gitee/baidu
* @param callback
* @return
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/{type}/callback")
public String login(@PathVariable String type, AuthCallback callback, Model model, HttpSession session) {
AuthRequest authRequest = factory.get(type);
AuthResponse<AuthUser> response = authRequest.login(callback);
log.info("登录回调 => {}", JSON.toJSONString(response));
if (response.ok()) {
JustAuthUser justAuthUser = new JustAuthUser(response.getData());
JustAuthUser queryJustAuthUser = justAuthUserService.getById(justAuthUser.getUuid());
// 无授权用户或者该授权用户与系统用户无绑定关系
if (queryJustAuthUser == null || queryJustAuthUser.getUserId() == null) {
justAuthUserService.saveOrUpdate(justAuthUser);
model.addAttribute("justAuthUser", justAuthUser);
return "userBinder";
}
session.setAttribute("user", userService.getById(queryJustAuthUser.getUserId()));
return "redirect:/index";
}
return "error/403";
}
/**
* 授权用户和系统用户绑定
*
* @param uuid 授权用户Uuid
* @param user 系统用户
* @param session
* @return
*/
@RequestMapping(value = "/userBinder/{uuid}")
@ResponseBody
public Response<String> userBinder(@PathVariable String uuid, User user, HttpSession session) {
if (StrUtil.isBlank(user.getUsername()) || StrUtil.isBlank(user.getPassword())) {
return Response.error("用户名称或密码不能为空");
}
boolean checkUserNameResult = userService.checkUserName(user.getUsername());
if (checkUserNameResult) {
return Response.error("用户不存在,请输入系统中已存在的用户");
}
User queryUser = new User();
queryUser.setUsername(user.getUsername());
queryUser = userService.getOne(new QueryWrapper<>(queryUser));
if (queryUser == null || !StrUtil.equals(queryUser.getUsername(), user.getUsername())
|| !BCrypt.checkpw(user.getPassword(), queryUser.getPassword())) {
return Response.error("用户名称或密码错误");
}
JustAuthUser justAuthUser = new JustAuthUser();
justAuthUser.setUuid(uuid);
justAuthUser.setUserId(queryUser.getId());
boolean update = justAuthUserService.updateById(justAuthUser);
log.info("授权用户(uuid){} 与系统用户(id)绑定 {}", uuid, queryUser.getId());
if (update) {
session.setAttribute("user", queryUser);
return Response.success("登录成功");
}
return Response.error("绑定系统用户异常");
}
}
- 登录添加第三方系统链接
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/login.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
</head>
<body class="login-wrap">
<div class="login-container">
<form class="login-form pb10">
<div class="input-group text-center text-gray">
<h2>欢迎登录</h2>
</div>
<div class="input-group">
<input type="text" id="username" class="input-field">
<label for="username" class="input-label">
<span class="label-title">用户名</span>
</label>
</div>
<div class="input-group">
<input type="password" id="password" class="input-field">
<label for="password" class="input-label">
<span class="label-title">密码</span>
</label>
</div>
<button type="button" class="login-button">登录<i class="ai ai-enter"></i></button>
<div class="input-group text-center pt20 pl0 pr0">
<a th:href="@{/oauth/login/gitee}"><span class="icon-gitee"></span></a>
<a th:href="@{/oauth/login/baidu}"><span class="icon-baidu"></span></a>
<a href="javascript:" class="disabled"><span class="icon-qq"></span></a>
<a href="javascript:" class="disabled"><span class="icon-github"></span></a>
</div>
</form>
</div>
</body>
</html>
<script>
layui.define(['element'],function(exports){
var $ = layui.$;
$('.input-field').on('change',function(){
var $this = $(this),
value = $.trim($this.val()),
$parent = $this.parent();
if(!isEmpty(value)){
$parent.addClass('field-focus');
}else{
$parent.removeClass('field-focus');
}
})
exports('login');
});
// 登录
var layer = layui.layer;
$(".login-button").click(function() {
var username = $("#username").val();
var password = $("#password").val();
if (isEmpty(username) || isEmpty(password)) {
layer.msg("用户名或密码不能为空", {icon: 2});
return ;
}
var loading = layer.load(1, {shade: [0.3, '#fff']});
$.ajax({
url : "[[@{/}]]login",
data : {username : username, password : password},
type : "post",
dataType : "json",
error : function(data) {
},
success : function(data) {
layer.close(loading);
if (data.code == 200) {
location.href = "[[@{/}]]index";
} else {
layer.msg(data.msg, {icon: 2});
}
}
});
});
function isEmpty(n) {
if (n == null || n == '' || typeof(n) == 'undefined') {
return true;
}
return false;
}
</script>
- 在resource/view目录下,新增userBinder.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/login.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
</head>
<body class="login-wrap">
<div class="login-container">
<form class="login-form">
<input type="hidden" id="uuid" th:value="${justAuthUser?.uuid}"/>
<div class="input-group text-center text-gray">
<h2>欢迎<b class="text-orange"> [[${justAuthUser?.nickname}]] </b>登录</h2>
</div>
<div class="input-group">
<input type="text" id="username" class="input-field">
<label for="username" class="input-label">
<span class="label-title">用户名</span>
</label>
</div>
<div class="input-group">
<input type="password" id="password" class="input-field">
<label for="password" class="input-label">
<span class="label-title">密码</span>
</label>
</div>
<button type="button" class="login-button">登录<i class="ai ai-enter"></i></button>
</form>
</div>
</body>
</html>
<script>
layui.define(['element'],function(exports){
var $ = layui.$;
$('.input-field').on('change',function(){
var $this = $(this),
value = $.trim($this.val()),
$parent = $this.parent();
if(!isEmpty(value)){
$parent.addClass('field-focus');
}else{
$parent.removeClass('field-focus');
}
})
exports('login');
});
// 登录
var layer = layui.layer;
$(".login-button").click(function() {
var uuid = $("#uuid").val();
var username = $("#username").val();
var password = $("#password").val();
if (isEmpty(username) || isEmpty(password)) {
layer.msg("用户名或密码不能为空", {icon: 2});
return ;
}
var loading = layer.load(1, {shade: [0.3, '#fff']});
$.ajax({
url : "[[@{/}]]oauth/userBinder/" + uuid,
data : {username : username, password : password},
type : "post",
dataType : "json",
error : function(data) {
},
success : function(data) {
layer.close(loading);
if (data.code == 200) {
location.href = "[[@{/}]]index";
} else {
layer.msg(data.msg, {icon: 2});
}
}
});
});
function isEmpty(n) {
if (n == null || n == '' || typeof(n) == 'undefined') {
return true;
}
return false;
}
</script>
5. 测试
- 登录
浏览器访问:http://127.0.0.1:8443 。 - 跳转到第三方系统登录
点击下方码云图标,使用码云账号登录(前提已在码云创建应用)。 - 绑定系统用户
第一次授权用户未与系统用户绑定,则跳转到绑定页面,输入系统存在的用户信息(user/123456),即可完成绑定。完成后跳转到首页。 - 退出,再一次测试登录
若登录的账号已存在绑定关系,则在第三方认证通过后直接调整到首页
6. 项目地址
spring-boot-layui-justauth-demo
SpringBoot + Layui + JustAuth +Mybatis-plus实现可第三方登录的简单后台管理系统的更多相关文章
- SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)
1. 简介 layui(谐音:类UI)是一款采用自身模块规范编写的前端UI框架,遵循原生HTML/CSS/JS的书写与组织形式,门槛极低,拿来即用.其外在极简,却又不失饱满的内在,体积轻盈,组件丰 ...
- Android 集成支付宝第三方登录
前言: 在集成支付宝支付的时候遇到一点小麻烦,先在此记录供大家参考 1.授权 支付宝第三方登录需要在后台进行授权,在查看授权的时候我们一定要看清楚时候真的已经获得了权限(我在没有获取权限的情况下集成的 ...
- springboot学习笔记:11.springboot+shiro+mysql+mybatis(通用mapper)+freemarker+ztree+layui实现通用的java后台管理系统(权限管理+用户管理+菜单管理)
一.前言 经过前10篇文章,我们已经可以快速搭建一个springboot的web项目: 今天,我们在上一节基础上继续集成shiro框架,实现一个可以通用的后台管理系统:包括用户管理,角色管理,菜单管理 ...
- springboot+layui实现PC端用户的增删改查 & 整合mui实现app端的自动登录和用户的上拉加载 & HBuilder打包app并在手机端下载安装
springboot整合web开发的各个组件在前面已经有详细的介绍,下面是用springboot整合layui实现了基本的增删改查. 同时在学习mui开发app,也就用mui实现了一个简单的自动登录和 ...
- Springboot学习与mybatis逆向生成工具
最近H2数据库越用越觉得方便,在不同办公处无缝继续demo的感觉就是爽. 今天接上一篇Springboot简洁整合mybatis,补上sts(即eclipse)使用mybatis generato ...
- springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑)
springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑) 写在前面: 富文本编辑器,Multi-function Text Editor, 简称 MTE, 是一 ...
- SpringBoot中关于Mybatis使用的三个问题
SpringBoot中关于Mybatis使用的三个问题 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8495453.html 原本是要讲讲PostgreSQL ...
- SpringBoot之整合Mybatis范例
依赖包: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:/ ...
- springboot中使用mybatis显示执行sql
springboot 中使用mybatis显示执行sql的配置,在properties中添加如下 logging.你的包名=debug 2018-11-27 16:35:43.044 [DubboSe ...
随机推荐
- uniapp开发小程序
uniapp开发小程序 uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS.Android.Web(响应式).以及各种小程序(微信/支付宝/百度/头条 ...
- guitar pro 系列教程(十):关于Guitar Pro声部的使用技巧
作为一个刚接触吉他的萌音乐的玩家,我们在创作吉他谱时一定要注意其中的声部搭配,因为各个声部的配器音色和旋律会对我们的曲子有着非常重要的影响,而声部的把控技术,也可以体现一个音乐人的能力水平的一方面.今 ...
- 「LOJ 541」「LibreOJ NOIP Round #1」七曜圣贤
description 题面很长,这里给出题目链接 solution 用队列维护扔掉的红茶,同时若后扔出的红茶比先扔出的红茶编号更小,那么先扔出的红茶不可能成为答案,所以可以用单调队列维护 故每次询问 ...
- 模拟赛38 B. T形覆盖 大模拟
题目描述 如果玩过俄罗斯方块,应该见过如下图形: 我们称它为一个 \(T\) 形四格拼板 .其中心被标记为\(×\). 小苗画了一个 \(m\) 行 \(n\) 列的长方形网格.行从 \(0\) 至 ...
- DIV滚动条设置添加 CSS滚动条显示与滚动条隐藏
<!DOCTYPE html> <html> <head> <meta charset="gb2312" /> <title& ...
- javaAgent打包找不到premain类文件解决
agent 作用和开发 可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序.这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等. 由此可知agent ...
- mysql-查询不同列的数量合计
车辆违规信息表testmodel_test 表结构: 表字段:cra_id(车牌号),if_weigui(该次行驶是否违规,0是正常,1是违规) 目的: 查询表中共有几辆车,违规的有几辆车: 方法1 ...
- P2943 [USACO09MAR]Cleaning Up G
一句话题意:将一个数列分成若干段,每段的不和谐度为该段内不同数字数量的平方,求不和谐度之和的最小值. 令 \(f_i\) 表示前 \(i\) 个数的最小答案,很容易就能写出暴力转移方程:\(f_i=\ ...
- Java基础教程——Scanner类
Scanner属于java.util包. java.util包是Java内置的一个工具包,其中包含一系列常用的工具类,如处理日期.日历.集合类: 如果要使用到该包中的类,必须显式引入包名:import ...
- Java集合【3】-- iterable接口超级详细解析
目录 iterable接口 1. 内部定义的方法 1.1 iterator()方法 1.2 forEach()方法 1.3 spliterator()方法 总结 iterable接口 整个接口框架关系 ...