偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

SpringBoot 實(shí)現(xiàn)單點(diǎn)登錄:從傳統(tǒng)到現(xiàn)代的演進(jìn)之路

開發(fā) 前端
本文將深入探討在??SpringBoot??環(huán)境下實(shí)現(xiàn)??SSO??的四種主流方案:??Cookie-Session??模式、??JWT??無狀態(tài)模式、??OAuth 2.0??授權(quán)框架以及??Spring Session??分布式方案。

前言

在企業(yè)級(jí)應(yīng)用架構(gòu)中,單點(diǎn)登錄(Single Sign-On,SSO)已成為不可或缺的關(guān)鍵組件。它允許用戶只需一次登錄,就能訪問多個(gè)相互信任的應(yīng)用系統(tǒng),大幅提升了用戶體驗(yàn)與系統(tǒng)安全性。

本文將深入探討在SpringBoot環(huán)境下實(shí)現(xiàn)SSO的四種主流方案:Cookie-Session模式、JWT無狀態(tài)模式、OAuth 2.0授權(quán)框架以及Spring Session分布式方案。

什么是單點(diǎn)登錄?

單點(diǎn)登錄是一種身份認(rèn)證機(jī)制,其核心思想是:用戶在認(rèn)證中心完成一次登錄后,即可訪問所有信任該認(rèn)證中心的應(yīng)用系統(tǒng),無需重復(fù)登錄。這種機(jī)制帶來了多重優(yōu)勢(shì):

  • 提升用戶體驗(yàn):消除重復(fù)登錄的繁瑣過程
  • 增強(qiáng)安全性:集中管理認(rèn)證過程,便于實(shí)施統(tǒng)一的安全策略
  • 簡(jiǎn)化系統(tǒng)管理:集中式的用戶身份管理降低了運(yùn)維成本
  • 支持跨域認(rèn)證:解決不同域名應(yīng)用間的身份共享問題

實(shí)現(xiàn)

效果圖

圖片圖片

基于 Cookie-Session 的傳統(tǒng)實(shí)現(xiàn)

Cookie-Session模式是最經(jīng)典的SSO實(shí)現(xiàn)方式,依賴于服務(wù)器端存儲(chǔ)會(huì)話狀態(tài)和客戶端存儲(chǔ)標(biāo)識(shí)信息。

實(shí)現(xiàn)原理

  • 用戶訪問應(yīng)用系統(tǒng),發(fā)現(xiàn)未登錄,重定向到認(rèn)證中心
  • 用戶在認(rèn)證中心輸入credentials進(jìn)行登錄
  • 認(rèn)證中心驗(yàn)證通過后,創(chuàng)建Session存儲(chǔ)用戶信息,生成SessionID
  • 認(rèn)證中心將SessionID寫入Cookie,并重定向回原應(yīng)用系統(tǒng)
  • 應(yīng)用系統(tǒng)向認(rèn)證中心驗(yàn)證SessionID的有效性
  • 驗(yàn)證通過后,應(yīng)用系統(tǒng)可創(chuàng)建本地會(huì)話或直接信任該身份

示例代碼

認(rèn)證中心
@RestController
public class AuthController {
    
    @Autowired
    private HttpSession session;
    
    // 登錄接口
    @PostMapping("/login")
    public String login(@RequestParam String username, 
                       @RequestParam String password,
                       HttpServletResponse response) {
        // 驗(yàn)證用戶名密碼(實(shí)際應(yīng)用中應(yīng)連接數(shù)據(jù)庫)
        if ("admin".equals(username) && "admin123".equals(password)) {
            // 存儲(chǔ)用戶信息到Session
            session.setAttribute("user", username);
            // 設(shè)置SessionID到Cookie(跨域場(chǎng)景需要特殊配置)
            Cookie cookie = new Cookie("SESSIONID", session.getId());
            cookie.setDomain("example.com"); // 設(shè)置主域名,實(shí)現(xiàn)跨子域共享
            cookie.setPath("/");
            response.addCookie(cookie);
            return"登錄成功";
        }
        return"用戶名或密碼錯(cuò)誤";
    }
    
    // 驗(yàn)證Session有效性接口
    @GetMapping("/verify")
    public String verify(@CookieValue(value = "SESSIONID", required = false) String sessionId) {
        if (sessionId == null) {
            return"未登錄";
        }
        // 實(shí)際應(yīng)用中應(yīng)通過SessionRepository查找對(duì)應(yīng)Session
        if (session.getId().equals(sessionId) && session.getAttribute("user") != null) {
            return session.getAttribute("user").toString();
        }
        return"無效會(huì)話";
    }
}
客戶端應(yīng)用集成
@Configuration
public class SsoConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   Object handler) throws Exception {
                // 檢查是否為公開路徑
                String path = request.getRequestURI();
                if (path.contains("/login") || path.contains("/public")) {
                    returntrue;
                }
                
                // 從Cookie獲取SESSIONID
                String sessionId = null;
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if ("SESSIONID".equals(cookie.getName())) {
                            sessionId = cookie.getValue();
                            break;
                        }
                    }
                }
                
                // 調(diào)用認(rèn)證中心驗(yàn)證
                if (sessionId != null) {
                    RestTemplate restTemplate = new RestTemplate();
                    String username = restTemplate.getForObject(
                        "http://auth.example.com/verify?sessinotallow=" + sessionId, 
                        String.class);
                    if (!"未登錄".equals(username) && !"無效會(huì)話".equals(username)) {
                        // 驗(yàn)證通過,存儲(chǔ)用戶信息到本地
                        request.setAttribute("currentUser", username);
                        returntrue;
                    }
                }
                
                // 驗(yàn)證失敗,重定向到認(rèn)證中心
                response.sendRedirect("http://auth.example.com/login?redirect=" + 
                                     URLEncoder.encode(request.getRequestURL().toString(), "UTF-8"));
                returnfalse;
            }
        });
    }
}

優(yōu)點(diǎn):

  • 實(shí)現(xiàn)簡(jiǎn)單,易于理解和開發(fā)
  • 會(huì)話狀態(tài)存儲(chǔ)在服務(wù)器,安全性較高
  • 支持主動(dòng)使會(huì)話失效

缺點(diǎn):

  • 分布式環(huán)境下需要解決Session共享問題
  • 跨域場(chǎng)景處理復(fù)雜,Cookie存在跨域限制
  • 服務(wù)器存儲(chǔ)會(huì)話狀態(tài),增加了服務(wù)器負(fù)擔(dān)
  • 不適合前后端分離和移動(dòng)端應(yīng)用

基于 JWT 的無狀態(tài)實(shí)現(xiàn)

JWTJSON Web Token)是一種輕量級(jí)的認(rèn)證令牌,它將用戶信息編碼到令牌中,實(shí)現(xiàn)了無狀態(tài)的認(rèn)證機(jī)制,非常適合分布式系統(tǒng)。

實(shí)現(xiàn)原理

  • 用戶在認(rèn)證中心登錄成功后,服務(wù)器生成包含用戶信息的JWT令牌
  • 認(rèn)證中心將JWT返回給客戶端(通常存儲(chǔ)在localStorageCookie中)
  • 客戶端后續(xù)請(qǐng)求在Authorization頭中攜帶JWT
  • 各應(yīng)用系統(tǒng)接收到請(qǐng)求后,驗(yàn)證JWT的簽名有效性
  • 驗(yàn)證通過后,從JWT中解析出用戶信息,無需與認(rèn)證中心交互

JWT 結(jié)構(gòu)解析

  • Header(頭部):指定令牌類型和簽名算法
  • Payload(載荷):包含聲明信息,如用戶ID、角色、過期時(shí)間等
  • Signature(簽名):使用服務(wù)器密鑰對(duì)前兩部分進(jìn)行簽名,確保令牌未被篡改

示例代碼

JWT 工具類
@Component
public class JwtUtils {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private long expiration; // 單位:毫秒
    
    // 生成JWT令牌
    public String generateToken(String username) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    // 從JWT令牌中獲取用戶名
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    // 驗(yàn)證JWT令牌
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            returntrue;
        } catch (Exception e) {
            // 令牌無效或已過期
            returnfalse;
        }
    }
}
認(rèn)證中心
@RestController
public class AuthController {
    
    @Autowired
    private JwtUtils jwtUtils;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestParam String username, 
                                  @RequestParam String password) {
        // 驗(yàn)證用戶名密碼
        if ("admin".equals(username) && "admin123".equals(password)) {
            // 生成JWT令牌
            String token = jwtUtils.generateToken(username);
            return ResponseEntity.ok(new JwtResponse(token));
        }
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("認(rèn)證失敗");
    }
    
    // JWT響應(yīng)實(shí)體
    static class JwtResponse {
        private String token;
        
        public JwtResponse(String token) {
            this.token = token;
        }
        
        // getter and setter
    }
}
客戶端應(yīng)用
@Component
public class JwtInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtUtils jwtUtils;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        // 獲取Authorization頭
        String authorizationHeader = request.getHeader("Authorization");
        
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring(7);
            
            // 驗(yàn)證令牌
            if (jwtUtils.validateToken(token)) {
                String username = jwtUtils.getUsernameFromToken(token);
                // 將用戶信息存入請(qǐng)求
                request.setAttribute("currentUser", username);
                returntrue;
            }
        }
        
        // 令牌無效,返回401
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("Unauthorized");
        returnfalse;
    }
}

優(yōu)點(diǎn):

  • 無狀態(tài)設(shè)計(jì),服務(wù)器無需存儲(chǔ)會(huì)話信息,易于水平擴(kuò)展
  • 適合分布式系統(tǒng)和微服務(wù)架構(gòu)
  • 支持跨域認(rèn)證,適用于前后端分離和移動(dòng)端應(yīng)用
  • 減少了服務(wù)間的通信開銷

缺點(diǎn):

  • 令牌一旦生成無法直接修改或撤銷,除非維護(hù)黑名單
  • 令牌包含用戶信息,雖有簽名但不宜存儲(chǔ)敏感數(shù)據(jù)
  • 令牌過長(zhǎng)可能增加網(wǎng)絡(luò)傳輸負(fù)擔(dān)
  • 續(xù)簽機(jī)制相對(duì)復(fù)雜

基于 OAuth 2.0 的授權(quán)框架

OAuth 2.0是一個(gè)開放標(biāo)準(zhǔn)的授權(quán)框架,不僅用于單點(diǎn)登錄,更廣泛應(yīng)用于第三方應(yīng)用授權(quán)場(chǎng)景(如使用微信、QQ登錄其他應(yīng)用)。

核心概念

  • 資源所有者:通常指用戶,擁有可訪問的資源
  • 客戶端:請(qǐng)求訪問資源的應(yīng)用程序
  • 授權(quán)服務(wù)器:負(fù)責(zé)認(rèn)證用戶并頒發(fā)令牌
  • 資源服務(wù)器:存儲(chǔ)受保護(hù)資源的服務(wù)器,驗(yàn)證令牌有效性

授權(quán)流程(授權(quán)碼模式)

  • 客戶端引導(dǎo)用戶到授權(quán)服務(wù)器
  • 用戶在授權(quán)服務(wù)器進(jìn)行認(rèn)證并授予權(quán)限
  • 授權(quán)服務(wù)器返回授權(quán)碼給客戶端
  • 客戶端使用授權(quán)碼向授權(quán)服務(wù)器請(qǐng)求訪問令牌
  • 授權(quán)服務(wù)器驗(yàn)證授權(quán)碼,頒發(fā)訪問令牌(可能包含刷新令牌)
  • 客戶端使用訪問令牌訪問資源服務(wù)器
  • 資源服務(wù)器驗(yàn)證令牌,返回受保護(hù)資源

示例代碼

授權(quán)服務(wù)器配置
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            // 客戶端ID和密鑰
            .withClient("client-app")
            .secret(passwordEncoder().encode("client-secret"))
            // 授權(quán)類型
            .authorizedGrantTypes("authorization_code", "refresh_token")
            // 授權(quán)范圍
            .scopes("read", "write")
            // 回調(diào)地址
            .redirectUris("http://client.example.com/callback")
            // 訪問令牌有效期
            .accessTokenValiditySeconds(3600)
            // 刷新令牌有效期
            .refreshTokenValiditySeconds(86400);
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
資源服務(wù)器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated();
    }
}
客戶端應(yīng)用配置
@Configuration
@EnableOAuth2Sso
public class ClientConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/", "/login**").permitAll()
            .anyRequest().authenticated()
            .and()
            .logout()
            .logoutSuccessUrl("http://auth.example.com/logout")
            .permitAll();
    }
}
客戶端屬性配置
# OAuth2客戶端注冊(cè)配置(針對(duì)具體客戶端)
spring.security.oauth2.client.registration.my-client.client-id=client-app
spring.security.oauth2.client.registration.my-client.client-secret=client-secret
# 授權(quán)類型(根據(jù)實(shí)際場(chǎng)景選擇,如authorization_code、password等)
spring.security.oauth2.client.registration.my-client.authorization-grant-type=authorization_code
# 回調(diào)地址(需與認(rèn)證服務(wù)器配置的一致)
spring.security.oauth2.client.registration.my-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}

# 認(rèn)證服務(wù)器(Provider)配置
spring.security.oauth2.client.provider.my-provider.access-token-uri=http://auth.example.com/oauth/token
spring.security.oauth2.client.provider.my-provider.authorization-uri=http://auth.example.com/oauth/authorize
# 用戶信息端點(diǎn)(用于獲取登錄用戶詳情)
spring.security.oauth2.client.provider.my-provider.user-info-uri=http://auth.example.com/user
# 從用戶信息響應(yīng)中提取用戶名的字段(默認(rèn)是username,根據(jù)實(shí)際響應(yīng)調(diào)整)
spring.security.oauth2.client.provider.my-provider.user-name-attribute=username

優(yōu)點(diǎn):

  • 標(biāo)準(zhǔn)化協(xié)議,生態(tài)完善,支持多種授權(quán)模式
  • 安全性高,支持精細(xì)的權(quán)限控制
  • 非常適合第三方應(yīng)用授權(quán)場(chǎng)景
  • 支持令牌刷新機(jī)制

缺點(diǎn):

  • 實(shí)現(xiàn)相對(duì)復(fù)雜,學(xué)習(xí)成本較高
  • 流程相對(duì)繁瑣,增加了網(wǎng)絡(luò)請(qǐng)求次數(shù)
  • 不適合對(duì)性能要求極高的內(nèi)部系統(tǒng)

基于 Spring Session 的分布式實(shí)現(xiàn)

Spring Session提供了一種簡(jiǎn)化的方式來管理用戶會(huì)話,支持將會(huì)話數(shù)據(jù)存儲(chǔ)在分布式環(huán)境中(如 Redis、Mongo 等),非常適合集群部署的SSO系統(tǒng)。

實(shí)現(xiàn)原理

  • 擴(kuò)展了HttpSession,將會(huì)話數(shù)據(jù)存儲(chǔ)在外部數(shù)據(jù)源
  • 各應(yīng)用節(jié)點(diǎn)通過統(tǒng)一的SessionID訪問共享的會(huì)話數(shù)據(jù)
  • 支持跨域會(huì)話共享,解決了傳統(tǒng)Cookie-Session的分布式問題

示例代碼

配置屬性
# application.properties
spring.session.store-type=redis
spring.session.redis.namespace=spring:session:sso
server.servlet.session.cookie.name=SSOSESSION
server.servlet.session.cookie.domain=example.com
server.servlet.session.timeout=30m
認(rèn)證中心登錄
@RestController
public class AuthController {
    
    @PostMapping("/login")
    public String login(@RequestParam String username, 
                       @RequestParam String password,
                       HttpSession session) {
        // 驗(yàn)證用戶名密碼
        if ("admin".equals(username) && "admin123".equals(password)) {
            // 存儲(chǔ)用戶信息到Session
            session.setAttribute("user", username);
            return"登錄成功,SessionID: " + session.getId();
        }
        return"認(rèn)證失敗";
    }
    
    @GetMapping("/user")
    public String getUser(HttpSession session) {
        return session.getAttribute("user") != null ? 
               session.getAttribute("user").toString() : "未登錄";
    }
}
客戶端應(yīng)用
@RestController
public class ClientController {
    
    @GetMapping("/hello")
    public String hello(HttpSession session) {
        String user = (String) session.getAttribute("user");
        if (user != null) {
            return "Hello, " + user + "! 這是客戶端應(yīng)用";
        }
        return "請(qǐng)先登錄";
    }
}

優(yōu)點(diǎn):

  • 透明集成Spring生態(tài),無需大量修改現(xiàn)有代碼
  • 完美解決分布式系統(tǒng)的Session共享問題
  • 支持多種存儲(chǔ)方式,易于擴(kuò)展
  • 保留了傳統(tǒng)Session的使用習(xí)慣,學(xué)習(xí)成本低

缺點(diǎn):

  • 需要額外的存儲(chǔ)服務(wù)(如Redis
  • 仍依賴Cookie傳遞SessionID,跨域存在一定限制
  • 相比JWT增加了存儲(chǔ)訪問開銷

方案對(duì)比與選擇建議

方案

優(yōu)勢(shì)場(chǎng)景

缺點(diǎn)

適用系統(tǒng)

Cookie-Session

簡(jiǎn)單應(yīng)用、內(nèi)部系統(tǒng)

分布式支持差、跨域限制

小型單體應(yīng)用

JWT

前后端分離、移動(dòng)端、微服務(wù)

無法即時(shí)吊銷、存儲(chǔ)限制

分布式系統(tǒng)、API 服務(wù)

OAuth 2.0

第三方授權(quán)、開放平臺(tái)

實(shí)現(xiàn)復(fù)雜、流程長(zhǎng)

開放平臺(tái)、多客戶端場(chǎng)景

Spring Session

分布式集群、Session 共享

依賴外部存儲(chǔ)

集群部署的 Web 應(yīng)用


責(zé)任編輯:武曉燕 來源: 一安未來
相關(guān)推薦

2022-06-08 16:55:56

服務(wù)器Redis架構(gòu)

2023-07-26 15:25:55

供應(yīng)鏈4.0工業(yè)4.0

2021-06-17 16:24:49

云計(jì)算軟件開發(fā)

2023-08-29 08:00:38

2025-03-04 09:02:25

JavaSPI機(jī)制

2020-12-28 05:52:27

SSO登錄單點(diǎn)

2024-12-24 07:20:00

C++std::anyC++17

2025-08-28 01:12:00

狀態(tài)機(jī)系統(tǒng)編排

2022-06-02 08:37:10

架構(gòu)DDDMVC

2023-09-26 00:29:40

CSS布局標(biāo)簽

2023-07-02 11:14:21

工具TypeScript框架

2024-06-21 09:28:43

2015-04-07 11:05:15

VMwareOpenStack

2016-12-26 18:05:00

單點(diǎn)登錄原理簡(jiǎn)單實(shí)現(xiàn)

2024-05-16 07:51:55

分布式系統(tǒng)架構(gòu)

2021-06-24 08:52:19

單點(diǎn)登錄代碼前端

2021-07-02 10:45:53

SpringBootCAS登錄

2025-10-27 08:25:01

2025-06-06 01:15:00

2022-07-01 08:26:22

區(qū)塊鏈去中心化以太坊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)