mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-18 09:35:29 +08:00
sso service
This commit is contained in:
parent
3443f5fc4e
commit
1510f110f4
92
ruoyi-modules/ruoyi-sso-server/pom.xml
Normal file
92
ruoyi-modules/ruoyi-sso-server/pom.xml
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<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">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-modules</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>ruoyi-sso-server</artifactId>
|
||||||
|
|
||||||
|
<description> sso-server </description>
|
||||||
|
|
||||||
|
<!-- 定义 Sa-Token 版本号 -->
|
||||||
|
<properties>
|
||||||
|
<sa-token.version>1.38.0</sa-token.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 没有loadbalancer调用会报错 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.openfeign</groupId>
|
||||||
|
<artifactId>feign-okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringBoot Web依赖 -->
|
||||||
|
<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.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 缓存服务 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- satoken -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-satoken</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 安全模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-security</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 租户模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-tenant</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 插件:整合SSO -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-sso</artifactId>
|
||||||
|
<version>1.38.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.pj;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.sso.SaSsoManager;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SaSsoServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(SaSsoServerApplication.class, args);
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------");
|
||||||
|
System.out.println("配置信息:" + SaSsoManager.getServerConfig());
|
||||||
|
System.out.println("统一认证登录地址:http://sa-sso-server.com:9000/sso/auth");
|
||||||
|
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.pj.h5;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.sso.util.SaSsoConsts;
|
||||||
|
import cn.dev33.satoken.sso.template.SaSsoUtil;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.util.SaFoxUtil;
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前后台分离架构下集成SSO所需的代码 (SSO-Server端)
|
||||||
|
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
|
||||||
|
* @author click33
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class H5Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 redirectUrl
|
||||||
|
*/
|
||||||
|
@RequestMapping("/sso/getRedirectUrl")
|
||||||
|
public SaResult getRedirectUrl(String redirect, String mode, String client) {
|
||||||
|
// 未登录情况下,返回 code=401
|
||||||
|
if(!StpUtil.isLogin()) {
|
||||||
|
return SaResult.code(401);
|
||||||
|
}
|
||||||
|
// 已登录情况下,构建 redirectUrl
|
||||||
|
redirect = SaFoxUtil.decoderUrl(redirect);
|
||||||
|
if(SaSsoConsts.MODE_SIMPLE.equals(mode)) {
|
||||||
|
// 模式一
|
||||||
|
SaSsoUtil.checkRedirectUrl(redirect);
|
||||||
|
return SaResult.data(redirect);
|
||||||
|
} else {
|
||||||
|
// 模式二或模式三
|
||||||
|
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
|
||||||
|
return SaResult.data(redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.pj.home;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.context.SaHolder;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSO 平台中心模式示例,跳连接进入子系统
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class HomeController {
|
||||||
|
|
||||||
|
// 平台化首页
|
||||||
|
@RequestMapping("/home")
|
||||||
|
public Object index() {
|
||||||
|
// 如果未登录,则先去登录
|
||||||
|
if(!StpUtil.isLogin()) {
|
||||||
|
return SaHolder.getResponse().redirect("/sso/auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接各个子系统的地址,格式形如:/sso/auth?redirect=${子系统首页}/sso/login?back=${子系统首页}
|
||||||
|
String link1 = "/sso/auth?redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/";
|
||||||
|
String link2 = "/sso/auth?redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/";
|
||||||
|
String link3 = "/sso/auth?redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/";
|
||||||
|
|
||||||
|
// 组织网页结构返回到前端
|
||||||
|
String title = "<h2>SSO 平台首页</h2>";
|
||||||
|
String client1 = "<p><a href='" + link1 + "' target='_blank'> 进入Client1系统 </a></p>";
|
||||||
|
String client2 = "<p><a href='" + link2 + "' target='_blank'> 进入Client2系统 </a></p>";
|
||||||
|
String client3 = "<p><a href='" + link3 + "' target='_blank'> 进入Client3系统 </a></p>";
|
||||||
|
|
||||||
|
return title + client1 + client2 + client3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.pj.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RoleDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色ID
|
||||||
|
*/
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色名称
|
||||||
|
*/
|
||||||
|
private String roleName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限
|
||||||
|
*/
|
||||||
|
private String roleKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
|
||||||
|
*/
|
||||||
|
private String dataScope;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.pj.model.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码信息
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CaptchaVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启验证码
|
||||||
|
*/
|
||||||
|
private Boolean captchaEnabled = true;
|
||||||
|
|
||||||
|
private String uuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码图片
|
||||||
|
*/
|
||||||
|
private String img;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.pj.model.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录验证信息
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LoginVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权令牌
|
||||||
|
*/
|
||||||
|
@JsonProperty("access_token")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌
|
||||||
|
*/
|
||||||
|
@JsonProperty("refresh_token")
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权令牌 access_token 的有效期
|
||||||
|
*/
|
||||||
|
@JsonProperty("expire_in")
|
||||||
|
private Long expireIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌 refresh_token 的有效期
|
||||||
|
*/
|
||||||
|
@JsonProperty("refresh_expire_in")
|
||||||
|
private Long refreshExpireIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用id
|
||||||
|
*/
|
||||||
|
@JsonProperty("client_id")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌权限
|
||||||
|
*/
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 openid
|
||||||
|
*/
|
||||||
|
private String openid;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package com.pj.model.vo;
|
||||||
|
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权管理视图对象 sys_client
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
* @date 2023-05-15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysClientVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端id
|
||||||
|
*/
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端key
|
||||||
|
*/
|
||||||
|
private String clientKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端秘钥
|
||||||
|
*/
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权类型
|
||||||
|
*/
|
||||||
|
private List<String> grantTypeList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权类型
|
||||||
|
*/
|
||||||
|
private String grantType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型
|
||||||
|
*/
|
||||||
|
private String deviceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token活跃超时时间
|
||||||
|
*/
|
||||||
|
private Long activeTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token固定超时时间
|
||||||
|
*/
|
||||||
|
private Long timeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(0正常 1停用)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.pj.model.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.dromara.common.core.constant.UserConstants;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色信息视图对象 sys_role
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysRoleVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色ID
|
||||||
|
*/
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色名称
|
||||||
|
*/
|
||||||
|
private String roleName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限字符串
|
||||||
|
*/
|
||||||
|
private String roleKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示顺序
|
||||||
|
*/
|
||||||
|
private Integer roleSort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)
|
||||||
|
*/
|
||||||
|
private String dataScope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单树选择项是否关联显示
|
||||||
|
*/
|
||||||
|
private Boolean menuCheckStrictly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门树选择项是否关联显示
|
||||||
|
*/
|
||||||
|
private Boolean deptCheckStrictly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色状态(0正常 1停用)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户是否存在此角色标识 默认不存在
|
||||||
|
*/
|
||||||
|
private boolean flag = false;
|
||||||
|
|
||||||
|
public boolean isSuperAdmin() {
|
||||||
|
return UserConstants.SUPER_ADMIN_ID.equals(this.roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package com.pj.model.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息视图对象 sys_user
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysUserVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户ID
|
||||||
|
*/
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户账号
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户类型(sys_user系统用户)
|
||||||
|
*/
|
||||||
|
private String userType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号码
|
||||||
|
*/
|
||||||
|
private String phonenumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户性别(0男 1女 2未知)
|
||||||
|
*/
|
||||||
|
private String sex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像地址
|
||||||
|
*/
|
||||||
|
private Long avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
@JsonProperty
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帐号状态(0正常 1停用)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录IP
|
||||||
|
*/
|
||||||
|
private String loginIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录时间
|
||||||
|
*/
|
||||||
|
private Date loginDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门名
|
||||||
|
*/
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色对象
|
||||||
|
*/
|
||||||
|
private List<SysRoleVo> roles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色组
|
||||||
|
*/
|
||||||
|
private Long[] roleIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 岗位组
|
||||||
|
*/
|
||||||
|
private Long[] postIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限 当前角色ID
|
||||||
|
*/
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.pj.service;
|
||||||
|
|
||||||
|
|
||||||
|
import com.pj.model.vo.LoginVo;
|
||||||
|
import com.pj.model.vo.SysClientVo;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权策略
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
public interface IAuthStrategy {
|
||||||
|
|
||||||
|
String BASE_NAME = "AuthStrategy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*
|
||||||
|
* @param body 登录对象
|
||||||
|
* @param client 授权管理视图对象
|
||||||
|
* @param grantType 授权类型
|
||||||
|
* @return 登录验证信息
|
||||||
|
*/
|
||||||
|
static LoginVo login(String body, SysClientVo client, String grantType) {
|
||||||
|
// 授权类型和客户端id
|
||||||
|
String beanName = grantType + IAuthStrategy.BASE_NAME;
|
||||||
|
if (!SpringUtils.containsBean(beanName)) {
|
||||||
|
throw new ServiceException("授权类型不正确!");
|
||||||
|
}
|
||||||
|
IAuthStrategy instance = SpringUtils.getBean(beanName);
|
||||||
|
return instance.login(body, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*
|
||||||
|
* @param body 登录对象
|
||||||
|
* @param client 授权管理视图对象
|
||||||
|
* @return 登录验证信息
|
||||||
|
*/
|
||||||
|
LoginVo login(String body, SysClientVo client);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
package com.pj.service.impl;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.SaLoginModel;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.lang.Opt;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.pj.model.vo.LoginVo;
|
||||||
|
import com.pj.model.vo.SysClientVo;
|
||||||
|
import com.pj.model.vo.SysRoleVo;
|
||||||
|
import com.pj.model.vo.SysUserVo;
|
||||||
|
import com.pj.service.IAuthStrategy;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.constant.Constants;
|
||||||
|
import org.dromara.common.core.constant.GlobalConstants;
|
||||||
|
import org.dromara.common.core.domain.dto.RoleDTO;
|
||||||
|
import org.dromara.common.core.domain.model.EmailLoginBody;
|
||||||
|
import org.dromara.common.core.domain.model.LoginUser;
|
||||||
|
import org.dromara.common.core.enums.LoginType;
|
||||||
|
import org.dromara.common.core.enums.UserStatus;
|
||||||
|
import org.dromara.common.core.exception.user.CaptchaExpireException;
|
||||||
|
import org.dromara.common.core.exception.user.UserException;
|
||||||
|
import org.dromara.common.core.utils.MessageUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.core.utils.ValidatorUtils;
|
||||||
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.common.tenant.helper.TenantHelper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件认证策略
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service("email" + IAuthStrategy.BASE_NAME)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class EmailAuthStrategy implements IAuthStrategy {
|
||||||
|
|
||||||
|
//后续改为远程的
|
||||||
|
// private final SysLoginService loginService;
|
||||||
|
// private final SysUserMapper userMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginVo login(String body, SysClientVo client) {
|
||||||
|
EmailLoginBody loginBody = JSONUtil.toBean(body, EmailLoginBody.class);
|
||||||
|
ValidatorUtils.validate(loginBody);
|
||||||
|
String tenantId = loginBody.getTenantId();
|
||||||
|
String email = loginBody.getEmail();
|
||||||
|
String emailCode = loginBody.getEmailCode();
|
||||||
|
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||||
|
// SysUserVo user = loadUserByEmail(email);
|
||||||
|
// loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
|
||||||
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
|
// return loginService.buildLoginUser(user);
|
||||||
|
return buildLoginUser();
|
||||||
|
});
|
||||||
|
loginUser.setClientKey(client.getClientKey());
|
||||||
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
|
SaLoginModel model = new SaLoginModel();
|
||||||
|
model.setDevice(client.getDeviceType());
|
||||||
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
|
model.setTimeout(client.getTimeout());
|
||||||
|
model.setActiveTimeout(client.getActiveTimeout());
|
||||||
|
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||||
|
// 生成token
|
||||||
|
LoginHelper.login(loginUser, model);
|
||||||
|
|
||||||
|
LoginVo loginVo = new LoginVo();
|
||||||
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
|
loginVo.setClientId(client.getClientId());
|
||||||
|
return loginVo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建登录用户
|
||||||
|
*/
|
||||||
|
private LoginUser buildLoginUser() {
|
||||||
|
LoginUser loginUser = new LoginUser();
|
||||||
|
loginUser.setTenantId("1");
|
||||||
|
loginUser.setUserId(1L);
|
||||||
|
loginUser.setDeptId(2L);
|
||||||
|
loginUser.setUsername("测试用户登录名");
|
||||||
|
loginUser.setNickname("测试用户名称");
|
||||||
|
loginUser.setUserType("用户类型1");
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 校验邮箱验证码
|
||||||
|
// */
|
||||||
|
// private boolean validateEmailCode(String tenantId, String email, String emailCode) {
|
||||||
|
// String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
|
||||||
|
// if (StringUtils.isBlank(code)) {
|
||||||
|
// loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||||
|
// throw new CaptchaExpireException();
|
||||||
|
// }
|
||||||
|
// return code.equals(emailCode);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private SysUserVo loadUserByEmail(String email) {
|
||||||
|
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
|
||||||
|
// if (ObjectUtil.isNull(user)) {
|
||||||
|
// log.info("登录用户:{} 不存在.", email);
|
||||||
|
// throw new UserException("user.not.exists", email);
|
||||||
|
// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||||
|
// log.info("登录用户:{} 已被停用.", email);
|
||||||
|
// throw new UserException("user.blocked", email);
|
||||||
|
// }
|
||||||
|
// return user;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
package com.pj.service.impl;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.SaLoginModel;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.pj.model.vo.LoginVo;
|
||||||
|
import com.pj.model.vo.SysClientVo;
|
||||||
|
import com.pj.service.IAuthStrategy;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.domain.model.LoginUser;
|
||||||
|
import org.dromara.common.core.domain.model.PasswordLoginBody;
|
||||||
|
import org.dromara.common.core.utils.ValidatorUtils;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.common.tenant.helper.TenantHelper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码认证策略
|
||||||
|
*
|
||||||
|
* @author Michelle.Chung
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service("password" + IAuthStrategy.BASE_NAME)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PasswordAuthStrategy implements IAuthStrategy {
|
||||||
|
|
||||||
|
// private final CaptchaProperties captchaProperties;
|
||||||
|
// private final SysLoginService loginService;
|
||||||
|
// private final SysUserMapper userMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginVo login(String body, SysClientVo client) {
|
||||||
|
PasswordLoginBody loginBody = JSONUtil.toBean(body, PasswordLoginBody.class);
|
||||||
|
ValidatorUtils.validate(loginBody);
|
||||||
|
String tenantId = loginBody.getTenantId();
|
||||||
|
String username = loginBody.getUsername();
|
||||||
|
String password = loginBody.getPassword();
|
||||||
|
String code = loginBody.getCode();
|
||||||
|
String uuid = loginBody.getUuid();
|
||||||
|
|
||||||
|
// boolean captchaEnabled = captchaProperties.getEnable();
|
||||||
|
// // 验证码开关
|
||||||
|
// if (captchaEnabled) {
|
||||||
|
// validateCaptcha(tenantId, username, code, uuid);
|
||||||
|
// }
|
||||||
|
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||||
|
// SysUserVo user = loadUserByUsername(username);
|
||||||
|
// loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||||
|
// // 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||||
|
// return loginService.buildLoginUser(user);
|
||||||
|
return buildLoginUser();
|
||||||
|
});
|
||||||
|
loginUser.setClientKey(client.getClientKey());
|
||||||
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
|
SaLoginModel model = new SaLoginModel();
|
||||||
|
model.setDevice(client.getDeviceType());
|
||||||
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
|
model.setTimeout(client.getTimeout());
|
||||||
|
model.setActiveTimeout(client.getActiveTimeout());
|
||||||
|
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||||
|
// 生成token
|
||||||
|
LoginHelper.login(loginUser, model);
|
||||||
|
|
||||||
|
LoginVo loginVo = new LoginVo();
|
||||||
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
|
loginVo.setClientId(client.getClientId());
|
||||||
|
return loginVo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginUser buildLoginUser() {
|
||||||
|
LoginUser loginUser = new LoginUser();
|
||||||
|
loginUser.setTenantId("1");
|
||||||
|
loginUser.setUserId(1L);
|
||||||
|
loginUser.setDeptId(2L);
|
||||||
|
loginUser.setUsername("测试用户登录名");
|
||||||
|
loginUser.setNickname("测试用户名称");
|
||||||
|
loginUser.setUserType("用户类型1");
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验验证码
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param code 验证码
|
||||||
|
* @param uuid 唯一标识
|
||||||
|
*/
|
||||||
|
// private void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||||
|
// String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
|
||||||
|
// String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||||
|
// RedisUtils.deleteObject(verifyKey);
|
||||||
|
// if (captcha == null) {
|
||||||
|
// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||||
|
// throw new CaptchaExpireException();
|
||||||
|
// }
|
||||||
|
// if (!code.equalsIgnoreCase(captcha)) {
|
||||||
|
// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||||
|
// throw new CaptchaException();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private SysUserVo loadUserByUsername(String username) {
|
||||||
|
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
|
||||||
|
// if (ObjectUtil.isNull(user)) {
|
||||||
|
// log.info("登录用户:{} 不存在.", username);
|
||||||
|
// throw new UserException("user.not.exists", username);
|
||||||
|
// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||||
|
// log.info("登录用户:{} 已被停用.", username);
|
||||||
|
// throw new UserException("user.blocked", username);
|
||||||
|
// }
|
||||||
|
// return user;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.pj.sso;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常处理
|
||||||
|
* @author click33
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
// 全局异常拦截
|
||||||
|
@ExceptionHandler
|
||||||
|
public SaResult handlerException(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return SaResult.error(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
package com.pj.sso;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.context.SaHolder;
|
||||||
|
import cn.dev33.satoken.context.model.SaRequest;
|
||||||
|
import cn.dev33.satoken.sign.SaSignUtil;
|
||||||
|
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
|
||||||
|
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.pj.model.vo.LoginVo;
|
||||||
|
import com.pj.model.vo.SysClientVo;
|
||||||
|
import com.pj.service.IAuthStrategy;
|
||||||
|
import org.dromara.common.core.domain.model.PasswordLoginBody;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sa-Token-SSO Server端 Controller
|
||||||
|
* @author click33
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class SsoServerController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSO-Server端:处理所有SSO相关请求
|
||||||
|
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
|
||||||
|
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
|
||||||
|
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
|
||||||
|
* http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、sign=参数签名
|
||||||
|
*/
|
||||||
|
@RequestMapping("/sso/*")
|
||||||
|
public Object ssoRequest() {
|
||||||
|
// 如果登录时没有提供redirect参数,则进入平台中心首页 /home,而不是重定向到 client 端
|
||||||
|
SaRequest req = SaHolder.getRequest();
|
||||||
|
if(req.isPath("/sso/auth") && !req.hasParam("redirect") && StpUtil.isLogin()) {
|
||||||
|
return SaHolder.getResponse().redirect("/home");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.isPath("/sso/isLogin")) {
|
||||||
|
return SaResult.data(StpUtil.isLogin());
|
||||||
|
}
|
||||||
|
|
||||||
|
return SaSsoServerProcessor.instance.dister();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置SSO相关参数
|
||||||
|
@Autowired
|
||||||
|
private void configSso(SaSsoServerConfig ssoServer) {
|
||||||
|
|
||||||
|
// 配置:未登录时返回的View
|
||||||
|
ssoServer.notLoginView = () -> {
|
||||||
|
return new ModelAndView("sa-login.html");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 配置:登录处理函数
|
||||||
|
ssoServer.doLoginHandle = (name, pwd) -> {
|
||||||
|
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||||
|
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||||
|
StpUtil.login(10001);
|
||||||
|
PasswordLoginBody body = new PasswordLoginBody();
|
||||||
|
body.setUsername("sa");
|
||||||
|
body.setPassword("123456");
|
||||||
|
body.setClientId("ClientId");
|
||||||
|
body.setGrantType("password");
|
||||||
|
body.setTenantId("1");
|
||||||
|
body.setCode("code1");
|
||||||
|
body.setUuid("qweqw");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SysClientVo client = new SysClientVo();
|
||||||
|
client.setId(1L);
|
||||||
|
client.setTimeout(10000L);
|
||||||
|
client.setActiveTimeout(1000000L);
|
||||||
|
|
||||||
|
LoginVo loginVo = IAuthStrategy.login(JSONUtil.toJsonStr(body), client, body.getGrantType());
|
||||||
|
|
||||||
|
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
|
||||||
|
}
|
||||||
|
return SaResult.error("登录失败!");
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例:获取数据接口(用于在模式三下,为 client 端开放拉取数据的接口)
|
||||||
|
@RequestMapping("/sso/getData")
|
||||||
|
public SaResult getData(String apiType, String loginId) {
|
||||||
|
System.out.println("---------------- 获取数据 ----------------");
|
||||||
|
System.out.println("apiType=" + apiType);
|
||||||
|
System.out.println("loginId=" + loginId);
|
||||||
|
|
||||||
|
// 校验签名:只有拥有正确秘钥发起的请求才能通过校验
|
||||||
|
SaSignUtil.checkRequest(SaHolder.getRequest());
|
||||||
|
|
||||||
|
// 自定义返回结果(模拟)
|
||||||
|
return SaResult.ok()
|
||||||
|
.set("id", loginId)
|
||||||
|
.set("name", "LinXiaoYu")
|
||||||
|
.set("sex", "女")
|
||||||
|
.set("age", 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
# 端口
|
||||||
|
server:
|
||||||
|
port: 9002
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: http://127.0.0.1:8848
|
||||||
|
namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b
|
||||||
|
group: ${spring.profiles.active}
|
||||||
|
openfeign:
|
||||||
|
okhttp:
|
||||||
|
enabled: true
|
||||||
|
httpclient:
|
||||||
|
connection-timeout: 5000
|
||||||
|
# Redis配置 (SSO模式一和模式二使用Redis来同步会话)
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
# Redis数据库索引(默认为0)
|
||||||
|
database: 0
|
||||||
|
# Redis服务器地址
|
||||||
|
host: 127.0.0.1
|
||||||
|
# Redis服务器连接端口
|
||||||
|
port: 6379
|
||||||
|
# Redis服务器连接密码(默认为空)
|
||||||
|
password: Lzw8800580/
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池最大连接数
|
||||||
|
max-active: 200
|
||||||
|
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 10
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
|
||||||
|
# Sa-Token配置
|
||||||
|
sa-token:
|
||||||
|
# token名称 (同时也是cookie名称)
|
||||||
|
token-name: Authorization
|
||||||
|
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||||
|
is-concurrent: true
|
||||||
|
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||||
|
is-share: false
|
||||||
|
# jwt秘钥
|
||||||
|
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
|
||||||
|
|
||||||
|
# ------- SSO-模式二相关配置
|
||||||
|
sso-server:
|
||||||
|
# Ticket有效期 (单位: 秒),默认五分钟
|
||||||
|
ticket-timeout: 300
|
||||||
|
# 所有允许的授权回调地址
|
||||||
|
allow-url: "*"
|
||||||
|
|
||||||
|
# ruoyi-security配置
|
||||||
|
security:
|
||||||
|
# 排除路径
|
||||||
|
excludes:
|
||||||
|
# 静态资源
|
||||||
|
- /*.html
|
||||||
|
- /**/*.html
|
||||||
|
- /**/*.css
|
||||||
|
- /**/*.js
|
||||||
|
# 公共路径
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /sso/auth
|
||||||
|
- /sso/doLogin
|
||||||
|
# swagger 文档配置
|
||||||
|
- /*/api-docs
|
||||||
|
- /*/api-docs/**
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
ruoyi-modules/ruoyi-sso-server/src/main/resources/static/sa-res/jquery.min.js
vendored
Normal file
2
ruoyi-modules/ruoyi-sso-server/src/main/resources/static/sa-res/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
|||||||
|
/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */
|
||||||
|
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 701 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,59 @@
|
|||||||
|
*{margin: 0; padding: 0;}
|
||||||
|
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
|
||||||
|
::-webkit-input-placeholder{color: #ccc;}
|
||||||
|
|
||||||
|
/* 视图盒子 */
|
||||||
|
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
|
||||||
|
/* 背景 EAEFF3 */
|
||||||
|
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
|
||||||
|
.bg-2{height: 50%; background-color: #EAEFF3;}
|
||||||
|
|
||||||
|
/* 渐变背景 */
|
||||||
|
/*.bg-1{
|
||||||
|
background-size: 500%;
|
||||||
|
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
|
||||||
|
animation: bganimation 30s infinite;
|
||||||
|
}
|
||||||
|
@keyframes bganimation{
|
||||||
|
0%{background-position: 0% 50%;}
|
||||||
|
50%{background-position: 100% 50%;}
|
||||||
|
100%{background-position: 0% 50%;}
|
||||||
|
} */
|
||||||
|
/* 背景 */
|
||||||
|
.bg-1{background: #101C34;}
|
||||||
|
.bg-2{background: #101C34;}
|
||||||
|
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
|
||||||
|
|
||||||
|
|
||||||
|
/* 内容盒子 */
|
||||||
|
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
|
||||||
|
|
||||||
|
/* 登录盒子 */
|
||||||
|
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
|
||||||
|
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
|
||||||
|
.login-box{display: flex; align-items: center; text-align: center;}
|
||||||
|
|
||||||
|
/* 表单 */
|
||||||
|
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
|
||||||
|
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
|
||||||
|
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
|
||||||
|
|
||||||
|
/* 输入框 */
|
||||||
|
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
|
||||||
|
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
|
||||||
|
.s-input{font-size: 12px;}
|
||||||
|
.s-input:focus{border-color: #409eff}
|
||||||
|
|
||||||
|
/* 登录按钮 */
|
||||||
|
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
|
||||||
|
.s-btn:hover{background-color: #50aEFF;}
|
||||||
|
|
||||||
|
/* 重置按钮 */
|
||||||
|
.reset-box{text-align: left; font-size: 12px;}
|
||||||
|
.reset-box a{text-decoration: none;}
|
||||||
|
.reset-box a:hover{text-decoration: underline;}
|
||||||
|
|
||||||
|
/* loading框样式 */
|
||||||
|
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
|
||||||
|
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
|
||||||
|
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
// sa
|
||||||
|
var sa = {};
|
||||||
|
|
||||||
|
sa.setToken = function (token) {
|
||||||
|
localStorage.setItem("Authorization", token)
|
||||||
|
};
|
||||||
|
sa.getToken = function () {
|
||||||
|
return "Bearer " + localStorage.getItem("Authorization")
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajaxSettings.beforeSend = function (xhr, request) {
|
||||||
|
xhr.setRequestHeader("Authorization", sa.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开loading
|
||||||
|
sa.loading = function (msg) {
|
||||||
|
layer.closeAll(); // 开始前先把所有弹窗关了
|
||||||
|
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 隐藏loading
|
||||||
|
sa.hideLoading = function () {
|
||||||
|
layer.closeAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "sso/isLogin",
|
||||||
|
type: "get",
|
||||||
|
success: function (res) {
|
||||||
|
debugger
|
||||||
|
console.log('返回数据:', res);
|
||||||
|
sa.hideLoading();
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
layer.msg('登录成功', {anim: 0, icon: 6});
|
||||||
|
} else {
|
||||||
|
layer.msg("检验未登录!", {anim: 6, icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, type, errorThrown) {
|
||||||
|
sa.hideLoading();
|
||||||
|
if (xhr.status == 0) {
|
||||||
|
return layer.alert('无法连接到服务器,请检查网络');
|
||||||
|
}
|
||||||
|
return layer.alert("异常:" + JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------------- 登录事件 -----------------------------------
|
||||||
|
|
||||||
|
$('.login-btn').click(function () {
|
||||||
|
sa.loading("正在登录...");
|
||||||
|
// 开始登录
|
||||||
|
setTimeout(function () {
|
||||||
|
$.ajax({
|
||||||
|
url: "sso/doLogin",
|
||||||
|
type: "post",
|
||||||
|
data: {
|
||||||
|
name: $('[name=name]').val(),
|
||||||
|
pwd: $('[name=pwd]').val()
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (res) {
|
||||||
|
console.log('返回数据:', res);
|
||||||
|
sa.hideLoading();
|
||||||
|
if (res.code === 200) {
|
||||||
|
layer.msg('登录成功', {anim: 0, icon: 6});
|
||||||
|
sa.setToken(res.data);
|
||||||
|
setTimeout(function () {
|
||||||
|
location.reload();
|
||||||
|
}, 800)
|
||||||
|
} else {
|
||||||
|
layer.msg(res.msg, {anim: 6, icon: 2});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, type, errorThrown) {
|
||||||
|
sa.hideLoading();
|
||||||
|
if (xhr.status == 0) {
|
||||||
|
return layer.alert('无法连接到服务器,请检查网络');
|
||||||
|
}
|
||||||
|
return layer.alert("异常:" + JSON.stringify(xhr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绑定回车事件
|
||||||
|
$('[name=name],[name=pwd]').bind('keypress', function (event) {
|
||||||
|
if (event.keyCode == "13") {
|
||||||
|
$('.login-btn').click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入框获取焦点
|
||||||
|
$("[name=name]").focus();
|
||||||
|
|
||||||
|
// 打印信息
|
||||||
|
var str = "This page is provided by Sa-Token, Please refer to: " + "https://sa-token.cc/";
|
||||||
|
console.log(str);
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<title>Sa-SSO-Server 认证中心-登录</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<base th:href="@{/}" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<link rel="stylesheet" href="./sa-res/login.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="view-box">
|
||||||
|
<div class="bg-1"></div>
|
||||||
|
<div class="bg-2"></div>
|
||||||
|
<div class="content-box">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="from-box">
|
||||||
|
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
|
||||||
|
<div class="from-item">
|
||||||
|
<input class="s-input" name="name" placeholder="请输入账号" />
|
||||||
|
</div>
|
||||||
|
<div class="from-item">
|
||||||
|
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
|
||||||
|
</div>
|
||||||
|
<div class="from-item">
|
||||||
|
<button class="s-input s-btn login-btn">登录</button>
|
||||||
|
</div>
|
||||||
|
<div class="from-item reset-box">
|
||||||
|
<a href="javascript: location.reload();" >刷新</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部 版权 -->
|
||||||
|
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
|
||||||
|
This page is provided by Sa-Token-SSO
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- scripts -->
|
||||||
|
<script src="./sa-res/jquery.min.js"></script>
|
||||||
|
<script src="./sa-res/layer/layer.js"></script>
|
||||||
|
<script src="./sa-res/login.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user