SpringBoot整合SSO(single sign on)单点登录
作者:Mr Kwan 发布时间:2022-02-02 03:39:40
标签:SpringBoot,SSO,单点登录
1、单点登录三种常见的方式
(1)Session广播机制(Session复制)
(2)使用Cookie+Redis实现
(3)使用token实现
2、单点登录介绍
举例:
(1)引入jwt依赖
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
(2)创建JWTUtils工具类
public class JwtUtils {
//token过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
//秘钥
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
//设置头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("user")
.setIssuedAt(new Date())
//设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token主体部分(这里使用id和nickname作为主体部分)
.claim("id", id)
.claim("nickname", nickname)
//加密方式
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效(直接通过APP_SECRET解析token)
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效(通过获取请求头信息获取token再使用APP_SECRET解析token)
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token字符串获取用户id(取出有效载荷中的用户信息)
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
3、单点登录实现
项目目录结构
UcenterMemberController
@RestController
@RequestMapping("/user/")
@CrossOrigin
public class UcenterMemberController {
@Autowired
private UcenterMemberService ucenterMemberService;
//登录
@PostMapping("login")
public ResponseResult login(@RequestBody MobileLoginRequest request) {
String token = ucenterMemberService.login(request);
return ResponseResult.success().data("token", token);
}
//注册
@PostMapping("register")
public ResponseResult register(@RequestBody RegisterRequest request) {
ucenterMemberService.register(request);
return ResponseResult.success().message("注册成功");
}
//根据token获取用户信息
@GetMapping("getUserInfo")
public ResponseResult getUserInfo(HttpServletRequest request) {
//调用jwt工具类的方法,根据request对象获取头信息,返回用户id
String id = JwtUtils.getMemberIdByJwtToken(request);
//根据用户id查询用户
UcenterMember member = ucenterMemberService.getById(id);
return ResponseResult.success().data("userInfo", member);
}
}
ServiceImpl
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {
@Autowired
private StringRedisTemplate redisTemplate;
//登录
@Override
public String login(MobileLoginRequest request) {
String phone = request.getPhone();
String password = request.getPassword();
if (StrUtil.isBlank(phone) || StrUtil.isBlank(password)) {
throw new GuliException(200001, "请输入用户名或者密码");
}
//根据输入的手机号码查找该用户信息
UcenterMember ucenterByPhone = this.baseMapper.selectOne(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
if (ucenterByPhone == null) {
throw new GuliException(200002, "该用户名不存在");
}
//如果用户存在比对数据库密码和用户输入的密码
if (!MD5Util.encrypt(password).equals(ucenterByPhone.getPassword())) {
throw new GuliException(200003, "密码输入错误");
}
String token = JwtUtils.getJwtToken(ucenterByPhone.getId(), ucenterByPhone.getNickname());
return token;
}
//注册
@Override
public void register(RegisterRequest request) {
String phone = request.getPhone();
String password = request.getPassword();
String nickName = request.getNickName();
String code = request.getCode();
if (StrUtil.isBlank(phone) || StrUtil.isBlank(password) || StrUtil.isBlank(nickName) || StrUtil.isBlank(code)) {
throw new GuliException(200001, "请填写相关信息");
}
//判断手机号是否重复
Integer count = baseMapper.selectCount(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
if (count > 0) {
throw new GuliException(200001, "账号已经存在请重新输入");
}
//验证code
String redisCode = redisTemplate.opsForValue().get(phone);
if (StrUtil.isBlank(redisCode)) {
throw new GuliException(200001, "验证码已经过期,请重新获取");
}
if (!redisCode.equals(code)) {
throw new GuliException(200001, "验证码错误");
}
UcenterMember ucenterByPhone = new UcenterMember();
ucenterByPhone.setMobile(phone);
ucenterByPhone.setPassword(MD5Util.encrypt(password));
ucenterByPhone.setNickname(nickName);
ucenterByPhone.setIsDisabled(false);
int insert = baseMapper.insert(ucenterByPhone);
if(insert<=0){
throw new GuliException(20001,"注册失败");
}
}
}
MD5加密算法工具类
public final class MD5Util {
public static String encrypt(String strSrc) {
try {
char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f'};
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
public static void main(String[] args) {
System.out.println(MD5Util.encrypt("111111"));
}
}
4、登录完成后在前端界面展示用户信息
(1)第一、二、四步:登录的方法(记得npm install js-cookie)
//登录的方法
submitLogin() {
//第一步 调用接口进行登录,返回token字符串
loginApi.submitLoginUser(this.user)
.then(response => {
//第二步 获取token字符串放到cookie里面
//第一个参数cookie名称,第二个参数值,第三个参数作用范围
cookie.set('user_token',response.data.data.token,{domain: 'localhost'})
//第四步 调用接口 根据token获取用户信息,为了首页面显示
loginApi.getLoginUserInfo()
.then(response => {
this.loginInfo = response.data.data.userInfo
//获取返回用户信息,放到cookie里面(主页在cookie中获取用户信息进行展示)
cookie.set('user_info',this.loginInfo,{domain: 'localhost'})
//跳转页面
window.location.href = "/";
})
})
},
(2)第三步:在request.js中编写前端请求 * (发送请求携带token)
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000 // 请求超时时间
})
// request *
service.interceptors.request.use(
config => {
if (cookie.get('user_token')) {
config.headers['token'] = cookie.get('user_token') // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
(3)第五步:主页显示用户信息(从cookie中获取用户信息)
//创建方法,从cookie获取用户信息
showInfo() {
//从cookie获取用户信息
var userStr = cookie.get('guli_ucenter')
// 把字符串转换json对象(js对象),因为后端传过来的是"{'name','lucy','age':18}"的格式
if(userStr) {
this.loginInfo = JSON.parse(userStr)
}
}
显示用户信息(根据userInfo中id来判断)
<ul class="h-r-login">
//cookie中没有用户信息,显示登录和注册
<li v-if="!loginInfo.id" id="no-login">
<a href="/login" rel="external nofollow" title="登录">
<em class="icon18 login-icon"> </em>
<span class="vam ml5">登录</span>
</a>
|
<a href="/register" rel="external nofollow" title="注册">
<span class="vam ml5">注册</span>
</a>
</li>
//cookie中有用户信息,显示用户头像、昵称和退出
<li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
<a href="/ucenter" rel="external nofollow" title>
<img
:src="loginInfo.avatar"
width="30"
height="30"
class="vam picImg"
alt
>
<span id="userName" class="vam disIb">{{ loginInfo.nickname }}</span>
</a>
<a href="javascript:void(0);" rel="external nofollow" title="退出" @click="logout()" class="ml5">退出</a>
</li>
</ul>
退出登录,清空cookie中的token和用户信息
//退出
logout() {
//清空cookie值
cookie.set('user_token','',{domain: 'localhost'})
cookie.set('user_info','',{domain: 'localhost'})
//回到首页面
window.location.href = "/";
}
}
来源:https://blog.csdn.net/qq_27462223/article/details/106763419


猜你喜欢
- 首先看一下泛型的基本语法访问修饰符 返回类型 泛型方法名 <T>(T 参数)1):无法在泛型方法内部给任何 T 类型创建实例的对
- 本文实例介绍了C 语言实现线程池,支持动态拓展和销毁,分享给大家供大家参考,具体内容如下实现功能1.初始化指定个数的线程2.使用链表来管理任
- 概述模板方法模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。那么什么是模板方法呢?我们看下模板方法的定义。一个具体方法而非
- 由Lombok的@AllArgsConstructor注解引发的错误需求:在Service实现中写了一个方法调用第三方接口同步数据。 功能代
- 本文实例讲述了Android通过SOCKET下载文件的方法。分享给大家供大家参考,具体如下:服务端代码import java.io.Buff
- 什么是程序集?1.程序集(assembly)是一个及一个以上托管模块,以及一些资源文件的逻辑组合。2.程序集是组件复用,以及实施安全策略和版
- 本文主要讲解安装AndroidStudio和配置环境变量遇到一些问题,以及解决方法。需要的软件:AndriodStudio安装包.java
- 本文实例讲述了Asp.net中C#使用Socket发送和接收TCP数据的方法,分享给大家供大家参考。具体实现方法如下:具体程序代码如下:us
- Java计算一段程序的运行时间介绍了两种方法,一种是毫秒级别的计算,另一种是更精确的纳秒级别的计算。毫秒级别计算时间  
- 一、javaBeanjavaBean:一种类的规格编写规范javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称
- using System;using System.Collections.Generic;using System.IO;using Sy
- zuul动态路由网关服务是流量的唯一入口。不能随便停服务。所以动态路由就显得尤为必要。数据库动态路由基于事件刷新机制热修改zuul的路由属性
- 本文实例讲述了C#使用XML序列化操作菜单的方法。分享给大家供大家参考。具体分析如下:之前的一篇文章《C#递归读取XML菜单数据的方法》没使
- 前言最近想体验下最新版本的SpringBoot,逛了下官网,发现SpringBoot目前最新版本已经是2.6.4了,版本更新确实够快的。之前
- 本文实例为大家分享了JAVA NIO实现简单聊天室功能的具体代码,供大家参考,具体内容如下服务端初始化一个ServerSocketChann
- 一、开篇通过对之前Java之路的了解之后,相信初学者们都对Java有了一个比较深印象的了解了。但是事情不能总停留在理论层面,还得多多实现,才
- 有时候我们在阅读PDF文档时会遇到这样一种情况:PDF文档页数比较多,但是又没有书签,所以我们不能根据书签快速了解文档所讲解的内容,也不能点
- 1.循环遍历private void GetControls(Control fatherControl){ Co
- 本文实例为大家分享了java实现PDF转图片的具体代码,供大家参考,具体内容如下1.首先利用maven引入所需jar包<depende
- 本文实例为大家分享了C#实现学生成绩管理系统的具体代码,供大家参考,具体内容如下使用链表写学生成绩管理系统链表可以灵活的展示增删改查下面是结