SpringBoot集成redis與session實現分布式單點登錄
前言:
由于考慮到cookie的安全性問題,就有了下面這個版本的sso
單點登錄 SSO(Single Sign On)
什么是單點登錄?
單點登錄的英文名叫做:Single Sign On(簡稱SSO),指在同一帳號平臺下的多個應用系統(tǒng)中,用戶只需登錄一次,即可訪問所有相互信任的系統(tǒng)。簡而言之,多個系統(tǒng),統(tǒng)一登陸。
我們可以這樣理解,在一個服務模塊登錄后,其他模塊無需再登錄
實現方式
- session廣播機制實現(老方法) ? 當模塊較多時,比較浪費資源;數據冗余,存在多份一樣的數據? session默認過期時間30分鐘
- 基于cookie+redis實現? 在項目中任何一個模塊登錄后,把數據放到兩個地方? redis:key:生成唯一隨機值(ip、用戶id等) value:用戶數據? cookie:存放redis生成的key值放到cookie? 訪問其他模塊,發(fā)送請求帶著cookie進行發(fā)送,服務器獲取cookie值,在redis中查詢,根據key進行查詢,如果找到就是登錄狀態(tài)
- 分布式session方式實現單點登錄流程運行:(1) 用戶第一次登錄時,將會話信息(用戶Id和用戶信息),比如以用戶Id為Key,寫入分布式Session;(2) 用戶再次登錄時,獲取分布式Session,是否有會話信息,如果沒有則調到登錄頁;(3) 一般采用Cache中間件實現,建議使用Redis,因此它有持久化功能,方便分布式Session宕機后,可以從持久化存儲中加載會話信息;(4) 存入會話時,可以設置會話保持的時間,比如15分鐘,超過后自動超時;結合Cache中間件,實現的分布式Session,可以很好的模擬Session會話。
- token驗證在項目某個模塊進行登錄,登錄之后,按照jwt規(guī)則生成字待串,把登錄之后用戶包含到生成字符串里面,把字符串返回
(1)可以把字符串通過cookie返回
(2)把字符串通過地址欄返回前端收到token之后將token存儲在自己的請求頭之中或者url后面,這樣每次請求都可以帶著token請求。再去訪問項目其他模塊,獲取地址欄或者請求頭里面的token,根據字符串獲取用戶信息。同時為了設置失效時間,可以將token放在redis中,設置失效時間,判斷過期。- CAS 中央認證服務
開發(fā)技術
- SpringBoot
- Redis
- Session
單點登錄實現流程
- 用戶在登錄時,登錄成功以后得到當前sessionid
- 將用戶信息存儲在redis里面,設置有效時間30分鐘,以key-value形式,sessionid作為key,登錄成功后的用戶信息作為value
- 訪問時通過攔截器攔截請求,判斷當前sessionid是否在redis里,再則延長壽命,不再提示身份過期
- 完成登錄驗證后,放行執(zhí)行訪問請求
實現案例
實現效果:使用nginx做輪詢分發(fā)請求,在任何一個服務登錄成功以后,在訪問其他服務時就不需要再去登錄
- 1,首先創(chuàng)建一個boot項目
- 2,導入pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>3,配置核心文件
注意:這里只有一個配置文件,若想啟動兩個端口。就在編輯頁面的 VM OPTIONS 里配置如下 -Dserver.port=8082
server:
port: 8081
## redis
#session存儲類型
spring:
application:
name: redis_cookie
redis:
host: 127.0.0.1
port: 6379
#沒用就填空
password:
jedis:
pool:
#連接池最大連接數
max-active: 8
#阻塞時間 (負表示沒有)
max-wait: -1
#最大空閑連接
max-idle: 8
#最小空閑連接
min-idle: 0
#連接超時時間
timeout: 30000
database: 04,編寫用戶類
package com.gxhh.redis_session.bean;
/**
* @Program: LoginDemo
* @ClassName User
* @Description: 用戶類
* @Author: liutao
* @Create: 2022/7/8 16:04
* @Version: 1.0
*/
public class User {
private String username;
private String pwd;
public User() {
}
public User(String username, String pwd) {
this.username = username;
this.pwd = pwd;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"username='" + username + ''' +
", pwd='" + pwd + ''' +
'}';
}
}5,編寫登錄接口和業(yè)務邏輯
package com.gxhh.redis_session.web;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxhh.redis_session.bean.User;
import com.gxhh.redis_session.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Program: redis_cookie
* @ClassName LoginController
* @Author: liutao
* @Description: 用戶登錄和測試接口
* @Create: 2022-07-09 19:50
* @Version 1.0
**/
@RestController
public class LoginController {
@Autowired
RedisTemplate redisTemplate;
@Autowired
CookieUtil CookieUtil;
@Value("${server.port}")
String port;
/**
* 登錄接口
* @param user User對象
* @return 提示信息
* @throws JsonProcessingException
*/
@PostMapping(value = "/doLogin", produces = "text/html;charset=utf-8")
public String login(HttpServletRequest request, HttpServletResponse response, User user) throws JsonProcessingException {
System.out.println(user);
ValueOperations ops = redisTemplate.opsForValue();
String s = request.getSession().getId();
if (redisTemplate.hasKey(s)) {//登錄過
return "重復登錄";
} else {//未登錄
if ("sso".equals(user.getUsername()) && "123456".equals(user.getPwd())) {
ObjectMapper om = new ObjectMapper();
ops.set(s, om.writeValueAsString(user));//將憑證存入Redis
redisTemplate.expire(s, 30, TimeUnit.MINUTES);//設置過期時間,30分鐘
return "登錄成功";
}else {
return "登錄失?。?;
}
}
}
/**
* 退出接口
* @return
* @throws JsonProcessingException
*/
@RequestMapping (value = "/logout", produces = "text/html;charset=utf-8")
public String logout(HttpServletRequest request, HttpServletResponse response, User user) throws JsonProcessingException {
System.out.println(user);
if(redisTemplate.delete(request.getSession().getId())){
request.getSession().invalidate();
return "成功退出,請登錄!";
}else {
return "系統(tǒng)異常!";
}
}
/**
* 測試接口
* @param
* @return
*/
@GetMapping("/hello")
public String hello(){
return "hello 我是端口"+port;
}
}6,配置WebMVC攔截器,攔截所有請求,只放行登錄接口
package com.gxhh.redis_session.config;
import com.gxhh.redis_session.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Program: redis_cookie
* @ClassName WebMVCConfig
* @Author: liutao
* @Description: WebMVC攔截器
* @Create: 2022-07-09 19:50
* @Version 1.0
**/
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//需要攔截的路徑
.excludePathPatterns("/doLogin","/login.html") ;//排除/doLogin路徑
}
}7,配置請求攔截器
package com.gxhh.redis_session.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxhh.redis_session.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
* @Program: redis_cookie
* @ClassName LoginInterceptor
* @Author: liutao
* @Description: 用戶登錄攔截器,校驗session,身份驗證
* @Create: 2022-07-09 19:50
* @Version 1.0
**/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
RedisTemplate redisTemplate;
@Autowired
CookieUtil CookieUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("攔截到請求:"+request.getRequestURI());
System.out.println("當前令牌:"+request.getSession().getId());
String s = request.getSession().getId();
System.out.println("登錄狀態(tài):"+redisTemplate.hasKey(s));
if (redisTemplate.hasKey(s)) {//延長登錄狀態(tài)
redisTemplate.expire(s, 30, TimeUnit.MINUTES);//設置過期時間,30分鐘
return true;
}else {//身份過期
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("身份過期,非法請求");
return false;
}
}
}8,nginx分發(fā)輪詢配置
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream mysvr{
server localhost:8081;
server localhost:8082;
}
server {
listen 8052;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
# location / {
# root html;
# index index.html index.htm;
# proxy_pass http://localhost:8011;
# }
location / {
# root html;
# index index.html index.htm;
proxy_pass http://mysvr;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ .php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ .php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /.ht {
# deny all;
#}
}看效果
先訪問測試接口:

然后再登錄:

訪問測試接口:


關閉瀏覽器后訪問:

到此這篇關于SpringBoot集成redis與session實現分布式單點登錄的文章就介紹到這了,更多相關SpringBoot集成redis 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java循環(huán)隊列與非循環(huán)隊列的區(qū)別總結
今天給大家?guī)淼氖顷P于Java的相關知識總結,文章圍繞著Java循環(huán)隊列與非循環(huán)隊列的區(qū)別展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下2021-06-06

