SpringBoot整合SSO(single sign on)單點(diǎn)登錄
1、單點(diǎn)登錄三種常見的方式
(1)Session廣播機(jī)制(Session復(fù)制)
(2)使用Cookie+Redis實(shí)現(xiàn)
(3)使用token實(shí)現(xiàn)

2、單點(diǎn)登錄介紹




舉例:

(1)引入jwt依賴
<!-- JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency>
(2)創(chuàng)建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()
//設(shè)置頭信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("user")
.setIssuedAt(new Date())
//設(shè)置過期時間
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//設(shè)置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;
}
/**
* 根據(jù)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、單點(diǎn)登錄實(shí)現(xiàn)
項(xiàng)目目錄結(jié)構(gòu)

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("注冊成功");
}
//根據(jù)token獲取用戶信息
@GetMapping("getUserInfo")
public ResponseResult getUserInfo(HttpServletRequest request) {
//調(diào)用jwt工具類的方法,根據(jù)request對象獲取頭信息,返回用戶id
String id = JwtUtils.getMemberIdByJwtToken(request);
//根據(jù)用戶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, "請輸入用戶名或者密碼");
}
//根據(jù)輸入的手機(jī)號碼查找該用戶信息
UcenterMember ucenterByPhone = this.baseMapper.selectOne(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
if (ucenterByPhone == null) {
throw new GuliException(200002, "該用戶名不存在");
}
//如果用戶存在比對數(shù)據(jù)庫密碼和用戶輸入的密碼
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, "請?zhí)顚懴嚓P(guān)信息");
}
//判斷手機(jī)號是否重復(fù)
Integer count = baseMapper.selectCount(new LambdaQueryWrapper<UcenterMember>().eq(UcenterMember::getMobile, phone));
if (count > 0) {
throw new GuliException(200001, "賬號已經(jīng)存在請重新輸入");
}
//驗(yàn)證code
String redisCode = redisTemplate.opsForValue().get(phone);
if (StrUtil.isBlank(redisCode)) {
throw new GuliException(200001, "驗(yàn)證碼已經(jīng)過期,請重新獲取");
}
if (!redisCode.equals(code)) {
throw new GuliException(200001, "驗(yàn)證碼錯誤");
}
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() {
//第一步 調(diào)用接口進(jìn)行登錄,返回token字符串
loginApi.submitLoginUser(this.user)
.then(response => {
//第二步 獲取token字符串放到cookie里面
//第一個參數(shù)cookie名稱,第二個參數(shù)值,第三個參數(shù)作用范圍
cookie.set('user_token',response.data.data.token,{domain: 'localhost'})
//第四步 調(diào)用接口 根據(jù)token獲取用戶信息,為了首頁面顯示
loginApi.getLoginUserInfo()
.then(response => {
this.loginInfo = response.data.data.userInfo
//獲取返回用戶信息,放到cookie里面(主頁在cookie中獲取用戶信息進(jìn)行展示)
cookie.set('user_info',this.loginInfo,{domain: 'localhost'})
//跳轉(zhuǎn)頁面
window.location.href = "/";
})
})
},
(2)第三步:在request.js中編寫前端請求攔截器(發(fā)送請求攜帶token)
// 創(chuàng)建axios實(shí)例
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 請根據(jù)實(shí)際情況自行修改
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
(3)第五步:主頁顯示用戶信息(從cookie中獲取用戶信息)
//創(chuàng)建方法,從cookie獲取用戶信息
showInfo() {
//從cookie獲取用戶信息
var userStr = cookie.get('guli_ucenter')
// 把字符串轉(zhuǎn)換json對象(js對象),因?yàn)楹蠖藗鬟^來的是"{'name','lucy','age':18}"的格式
if(userStr) {
this.loginInfo = JSON.parse(userStr)
}
}
顯示用戶信息(根據(jù)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 = "/";
}
}
到此這篇關(guān)于SpringBoot整合SSO(single sign on)單點(diǎn)登錄的文章就介紹到這了,更多相關(guān)SpringBoot整合SSO單點(diǎn)登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot使用Schedule實(shí)現(xiàn)定時任務(wù)的方法
這篇文章主要介紹了Spring?Boot使用Schedule實(shí)現(xiàn)定時任務(wù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
SpringBoot整合達(dá)夢數(shù)據(jù)庫的教程詳解
這篇文章主要給大家介紹了SpringBoot整合達(dá)夢數(shù)據(jù)庫的詳細(xì)教程,文章中有詳細(xì)的圖片介紹和代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下2023-08-08
解決springboot導(dǎo)入失敗,yml未識別的問題
這篇文章主要介紹了解決springboot導(dǎo)入失敗,yml未識別的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
Windows系統(tǒng)下Java連接SQL Server的方法簡介
這篇文章主要介紹了Windows系統(tǒng)下Java連接SQL Server的方法,分別是JDBC和JTDS的相關(guān)使用,需要的朋友可以參考下2015-09-09
Spring boot GC實(shí)現(xiàn)過程原理解析
這篇文章主要介紹了Spring boot GC實(shí)現(xiàn)過程原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08

