Sa-Token SSO 模式三
修改本地hosts
127.0.0.1 sa-sso-server.com
127.0.0.1 sa-sso-client1.com
127.0.0.1 sa-sso-client2.com
127.0.0.1 sa-sso-client3.com
使用的是源码里面的 sa-token-demo-sso-server
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.SaSsoUtil;
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;
import com.ejlchina.okhttps.OkHttps;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@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/logout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoHandle.serverRequest();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoConfig sso) {
// 配置:未登录时返回的View
sso.setNotLoginView(() -> {
return new ModelAndView("sa-login.html");
});
// 配置:登录处理函数
sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
});
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
sso.setSendHttp(url -> {
try {
// 发起 http 请求
System.out.println("发起请求:" + url);
return OkHttps.sync(url).get().getBody().toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
});
}
// 自定义接口:获取userinfo
@RequestMapping("/sso/userinfo")
public Object userinfo(String loginId) {
System.out.println("---------------- 获取userinfo --------");
// 校验签名,防止敏感信息外泄
SaSsoUtil.checkSign(SaHolder.getRequest());
// 自定义返回结果(模拟) name和pwd返回给client前端登录用,因为只有登录成功才返回密码,应该没安全问题
return SaResult.ok()
.set("id", loginId)
.set("name", "admin")
.set("pwd", "admin123")
.set("sex", "女")
.set("age", 18);
}
}
src/api/login.js
export function ssoLogout(satoken) {
return request({
url: '/auth/sso/logout',
headers: {
isToken: false
},
method: 'post',
data: {
satoken}
})
}
src/permission.js
增加 /sso
const whiteList = ['/sso']
src/router/index.js
{
path: '/sso',
component: () => import('@/views/sso'),
hidden: true
}
src/store/modules/user.js
import {
ssoLogout } from '@/api/login'
// 退出系统
LogOut({
commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
resolve()
// sso登录退出
let satoken = localStorage.satoken;
ssoLogout(satoken).then(res => {
});
}).catch(error => {
reject(error)
})
})
},
src/views/login.vue
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">若依后台管理系统</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
>
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item>
<!-- <el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleSsoLogin"
>
<span>登 录2</span>
</el-button>
</el-form-item>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleSsoLogout"
>
<span>注销</span>
</el-button>
</el-form-item>
<p>当前是否登录:<b>{
{
isLogin }}</b></p> -->
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright 2018-2022 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import {
getCodeImg, ssoLogout } from "@/api/login";
import Cookies from "js-cookie";
import {
encrypt, decrypt } from '@/utils/jsencrypt'
import request from '@/utils/request'
export default {
name: "Login",
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: "",
login: false,
},
loginRules: {
username: [
{
required: true, trigger: "blur", message: "请输入您的账号" }
],
password: [
{
required: true, trigger: "blur", message: "请输入您的密码" }
],
code: [{
required: true, trigger: "change", message: "请输入验证码" }]
},
loading: false,
// 验证码开关
captchaEnabled: true,
// 注册开关
register: false,
redirect: undefined,
saname:"",
sapwd:"",
isLogin:false
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
this.saname = route.query && route.query.saname;
this.sapwd = route.query && route.query.sapwd;
},
immediate: true
}
},
created() {
this.getCode();
this.getCookie();
// 是否自动登录
// this.ssoIsLogin();
},
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
},
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
};
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, {
expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), {
expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, {
expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({
path: this.redirect || "/" }).catch(()=>{
});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
},
handleSsoLogin(){
//let _url = location.href + '&saname='+ this.loginForm.username + '&sapwd='+ this.loginForm.password;
let _url = location.href;
this.$router.push({
path: '/sso', query: {
back: encodeURIComponent(_url) } });
},
handleSsoLogout(){
let satoken = localStorage.satoken;
ssoLogout(satoken).then(res => {
this.ssoIsLogin();
});
},
ssoIsLogin(){
request({
url: '/auth/sso/isLogin',
headers: {
isToken: false
},
method: 'post'
}).then(res => {
this.isLogin = res.data;
if(res.data){
console.log("sso登录成功");
// 跳转若依
this.handleRuoyiAutoLogin();
}
else{
console.log("sso未登录");
// 跳转sso登录
this.handleSsoLogin();
}
});
},
handleRuoyiAutoLogin(){
request({
url: '/auth/sso/myinfo',
headers: {
isToken: false
},
method: 'post'
}).then(res => {
if(res.code==200){
this.loginForm.username = res.name;
this.loginForm.password = res.pwd;
debugger;
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({
path: this.redirect || "/" }).catch(()=>{
});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
}
}
};
</script>
src/views/sso.vue
<template>
<div class="">
</div>
</template>
<script>
import {
getCodeImg, getRedirectUrl } from "@/api/login";
import Cookies from "js-cookie";
import {
encrypt, decrypt } from '@/utils/jsencrypt'
import request from '@/utils/request'
export default {
name: "Sso",
data() {
return {
baseUrl: "http://localhost/auth",
back: undefined,
ticket: undefined
};
},
watch: {
$route: {
handler: function(route) {
this.back = route.query && route.query.back;
this.ticket = route.query && route.query.ticket;
},
immediate: true
}
},
created() {
if(this.ticket) {
this.doLoginByTicket(this.ticket);
} else {
this.goSsoAuthUrl();
}
},
methods: {
goSsoAuthUrl() {
request({
url: '/auth/sso/getSsoAuthUrl',
headers: {
isToken: false
},
method: 'post',
params: {
clientLoginUrl:location.href }
}).then(res => {
location.href = res.data;
});
},
doLoginByTicket() {
request({
url: '/auth/sso/doLoginByTicket',
headers: {
isToken: false
},
method: 'post',
params: {
ticket: this.ticket }
}).then(res => {
if(res.code == 200) {
localStorage.setItem('satoken', res.data);
let _back = decodeURIComponent(this.back);
location.href = _back;
} else {
alert(res.msg);
}
});
},
}
};
</script>
<style rel="stylesheet/scss" lang="scss">
</style>
aihub-auth/pom.xml
<properties>
<sa-token-version>1.30.0</sa-token-version>
</properties>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 插件:整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>3.5.3</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>2.8.0</version>
</dependency>
aihub-auth/src/main/resources/bootstrap.yml
# sa-token配置
sa-token:
# SSO-相关配置
sso:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
# 使用Http请求校验ticket
is-http: true
# SSO-Server端 ticket校验地址
check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket
# 是否打开单点注销接口
is-slo: true
# 单点注销地址
slo-url: http://sa-sso-server.com:9000/sso/logout
# 接口调用秘钥
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# SSO-Server端 查询userinfo地址
userinfo-url: http://sa-sso-server.com:9000/sso/userinfo
# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
alone-redis:
# Redis数据库索引
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
aihub-auth/src/main/java/com/aihub/auth/controller/CorsFilter.java
package com.aihub.auth.controller;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 跨域过滤器
* @author kong
*/
@Component
@Order(-200)
public class CorsFilter implements Filter {
static final String OPTIONS = "OPTIONS";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 允许指定域访问跨域资源
response.setHeader("Access-Control-Allow-Origin", "*");
// 允许所有请求方式
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// 有效时间
response.setHeader("Access-Control-Max-Age", "3600");
// 允许的header参数
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");
// 如果是预检请求,直接返回
if (OPTIONS.equals(request.getMethod())) {
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
response.getWriter().print("");
return;
}
// System.out.println("*********************************过滤器被使用**************************");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
aihub-auth/src/main/java/com/aihub/auth/controller/H5Controller.java
package com.aihub.auth.controller;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
* @author kong
*
*/
@RestController
public class H5Controller {
/*
* SSO-Client端:处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client端登录地址,接受参数:back=登录后的跳转地址
* http://{host}:{port}/sso/logout -- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址
* http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoHandle.clientRequest();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoConfig sso) {
// 配置Http请求处理器
sso.setSendHttp(url -> {
System.out.println("发起请求:" + url);
return OkHttps.sync(url).get().getBody().toString();
});
}
// 当前是否登录
@RequestMapping("/sso/isLogin")
public Object isLogin() {
return SaResult.data(StpUtil.isLogin());
}
// 查询我的账号信息
@RequestMapping("/sso/myinfo")
public Object myinfo() {
Object userinfo = SaSsoUtil.getUserinfo(StpUtil.getLoginId());
System.out.println("--------info:" + userinfo);
return userinfo;
}
// 返回SSO认证中心登录地址
@RequestMapping("/sso/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
// 根据ticket进行登录
@RequestMapping("/sso/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoHandle.checkTicket(ticket, "/sso/doLoginByTicket");
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
}
return SaResult.error("无效ticket:" + ticket);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
aihub-gateway/src/main/java/com/aihub/gateway/filter/ValidateCodeFilter.java
//sso自动登录去掉验证码
private final static String[] VALIDATE_URL = new String[] {
"/auth/loginxxx", "/auth/register" };
文章浏览阅读8.8w次,点赞48次,收藏252次。Windows的用户文件夹默认所在位置是系统盘(通常是C盘)下的“\Users”目录之内。该文件夹中保存着所有的用户个人数据,比如你保存在“桌面”上的文件(实际上是保存在C:\Users\你的用户名\Desktop\目录之中),再比如你保存在“我的文档”里的文件(实际上是保存在C:\Users\用户名\Documents目录之中)。用户文件夹处于系统盘的坏处在于,如若系统盘一旦坏掉,就可能连带用..._该应用程序位于未经授权的位置 请移至非系统文件夹
文章浏览阅读6.7k次,点赞7次,收藏35次。约瑟夫环的数学方法解决 编写约瑟夫环程序时会发现,当我们把整个报数过程的人数N变的很大,例如到几百万,虽然在最后还是只剩下两个人报数,但也要循环几百万次才能确定最后留下来的那个人。这样程序执行的效率不高,会占用大量时间去执行循环的过程。有时会发生输出一直等待很长时间才能出来结果。经过查询资料,找到了约瑟夫环的数学解决方法,以及它的算法具体执行的过程来做分享。我们假设..._约瑟夫出圈问题公式
文章浏览阅读120次。1.薪资丰厚: 基本薪资+绩效+项目奖金+年终奖2.福利: 和正式员工福利基本看齐,共享工位,免费夜宵,班车,一流办公环境,月末周六双倍工资3.技术栈:C/C+当用例很多的时候,每次。不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦。不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦虑不焦。
文章浏览阅读871次,点赞11次,收藏3次。*单片机设计介绍, 基于51单片机冰箱温度控制器设计。_基于51单片机的智能冰箱控制系统设计
文章浏览阅读4.8k次。一、sftp服务器进入root模式(下面的操作默认都是在root用户下)#安装openssh-serverapt-get install -y openssh-server创建sftp的组和用户#创建sftp-users组groupadd sftp-users#创建sftp用户目录alicemkdir /home/alice#创建sftp用户alice,并且绑定其主目..._ubuntu sftp服务器查看用户和密码
文章浏览阅读5.9k次,点赞9次,收藏16次。解决了在simulink中使用s-function遇到的报错:State derivatives returned by S-function 'demo' in 'test/S-Function' during flag=1 call must be a real vector of length 2 _state derivatives returned by s-function 'pmsm' in 'ipmsm/ipmsm/s-function1
文章浏览阅读4.3w次,点赞79次,收藏882次。我们要使用这些知识实现一个简单的网页设计,利用HTML的a标签做文本内容跳转以及超链接的应用,CSS设计内容样式和图片、动画、视频的大小位置格式,JavaScript实现轮播图效果等。学习如何设计网页中的轮播图和动画效果,并掌握a标签文本内容跳转、超链接的应用、播放音乐与视频等操作。通过对Web知识内容的了解,我们掌握了HTML、CSS和JavaScript的基本知识以及利用它们实现一些简单的应用。1、使用Web知识实现一个简单的网页设计,利用HTML的a标签做文本内容跳转以及超链接的应用。_web前端网页设计代码
Matlab中讲解了如何约束ODE解为非负解的示例,并以绝对值函数和膝盖问题为例进行了说明。文章指出在某些情况下,由于方程的物理解释或解性质的原因,施加非负约束是必要的。
文章浏览阅读1.1k次。电脑上装的东西多了就很容引起版本或者依赖问题。。。这不,按照高博教程做octomap实验时候运行g2o_viewer data/result_after.g2o时候就直接coredump。。。。回想起来自己ROS系统中装了libg2o,于是卸载之:sudo apt-get remove ros-indigo-libg2o然后重新执行g2o_viewer data/result_after.g2o注..._libg2o_
文章浏览阅读2w次,点赞58次,收藏268次。学习通不想好好上课系列_学习通脚本
文章浏览阅读1.6k次。将Total Commander设置为“默认”文件管理器?法一:开始,运行,输入regedit ,回车: 定位到HKEY_LOCAL_MACHINE_total commander默认文件管理器
文章浏览阅读7.7k次。反序列化无法找到程序集提示找不到程序集. 原因是序列化时把序列化类的命名空间等信息保存了,但应用程序和类库的命名空间可能是不一样的,所以提示找不到程序集. 解决方法如下: 方法1.将dll加入强名称,注册到全局程序集缓存中 方法2.在反序列化使用的IFormatter 对象加入Binder 属性,使其获取要反序列化的对象所在的程序集_未能找到程序集“g:\c#%5cc#%20%e4%b8%8a%e4%bd%8d%e6%9c%ba%5c%e7%a9%ba%e5%8e%8